def addConstructor(self, valueNode, commentNode=None): entry = self.construct = {"line": (commentNode or valueNode).line} if commentNode is None: commentNode = valueNode # Root doc comment is optional for constructors comment = getDocComment(commentNode) if comment and comment.hasContent(): html = comment.getHtml(self.highlight) entry["doc"] = html entry["summary"] = Text.extractSummary(html) if comment and comment.tags: entry["tags"] = comment.tags entry["init"] = self.main["name"] funcParams = getParamNamesFromFunction(valueNode) if funcParams: entry["params"] = {} for paramPos, paramName in enumerate(funcParams): entry["params"][paramName] = {"position": paramPos} # Use comment for enrich existing data comment = getDocComment(commentNode) if comment: if not comment.params: self.warn( "Documentation for parameters of constructor are missing", valueNode.line) for paramName in funcParams: entry["params"][paramName]["errornous"] = True else: for paramName in funcParams: if paramName in comment.params: entry["params"][paramName].update( comment.params[paramName]) else: entry["params"][paramName]["errornous"] = True self.warn( "Missing documentation for parameter %s in constructor" % paramName, valueNode.line) else: entry["errornous"] = True
def addConstructor(self, valueNode, commentNode=None): entry = self.construct = { "line" : (commentNode or valueNode).line } if commentNode is None: commentNode = valueNode # Root doc comment is optional for constructors comment = getDocComment(commentNode) if comment and comment.hasContent(): html = comment.getHtml(self.highlight) entry["doc"] = html entry["summary"] = Text.extractSummary(html) if comment and comment.tags: entry["tags"] = comment.tags entry["init"] = self.main["name"] funcParams = getParamNamesFromFunction(valueNode) if funcParams: entry["params"] = {} for paramPos, paramName in enumerate(funcParams): entry["params"][paramName] = { "position" : paramPos } # Use comment for enrich existing data comment = getDocComment(commentNode) if comment: if not comment.params: self.warn("Documentation for parameters of constructor are missing", valueNode.line) for paramName in funcParams: entry["params"][paramName]["errornous"] = True else: for paramName in funcParams: if paramName in comment.params: entry["params"][paramName].update(comment.params[paramName]) else: entry["params"][paramName]["errornous"] = True self.warn("Missing documentation for parameter %s in constructor" % paramName, valueNode.line) else: entry["errornous"] = True
def addEvent(self, name, valueNode, commentNode, collection): entry = collection[name] = { "line" : (commentNode or valueNode).line } if valueNode.type == "dot": entry["type"] = assembleDot(valueNode) elif valueNode.type == "identifier": entry["type"] = valueNode.value # Try to resolve identifier with local variable assignment assignments, values = findAssignments(valueNode.value, valueNode) if assignments: # We prefer the same comment node as before as in these # szenarios a reference might be used for different event types if not findCommentNode(commentNode): commentNode = assignments[0] self.addEvent(name, values[0], commentNode, collection) return comment = getDocComment(commentNode) if comment: if comment.tags: entry["tags"] = comment.tags # Prefer type but fall back to returns (if the developer has made an error here) if comment.type: entry["type"] = comment.type elif comment.returns: entry["type"] = comment.returns[0] if comment.hasContent(): html = comment.getHtml(self.highlight) entry["doc"] = html entry["summary"] = Text.extractSummary(html) else: self.warn("Comment contains invalid HTML", commentNode.line) entry["errornous"] = True else: self.warn("Invalid doc comment", commentNode.line) entry["errornous"] = True
def addEvent(self, name, valueNode, commentNode, collection): entry = collection[name] = {"line": (commentNode or valueNode).line} if valueNode.type == "dot": entry["type"] = assembleDot(valueNode) elif valueNode.type == "identifier": entry["type"] = valueNode.value # Try to resolve identifier with local variable assignment assignments, values = findAssignments(valueNode.value, valueNode) if assignments: # We prefer the same comment node as before as in these # szenarios a reference might be used for different event types if not findCommentNode(commentNode): commentNode = assignments[0] self.addEvent(name, values[0], commentNode, collection) return comment = getDocComment(commentNode) if comment: if comment.tags: entry["tags"] = comment.tags # Prefer type but fall back to returns (if the developer has made an error here) if comment.type: entry["type"] = comment.type elif comment.returns: entry["type"] = comment.returns[0] if comment.hasContent(): html = comment.getHtml(self.highlight) entry["doc"] = html entry["summary"] = Text.extractSummary(html) else: self.warn("Comment contains invalid HTML", commentNode.line) entry["errornous"] = True else: self.warn("Invalid doc comment", commentNode.line) entry["errornous"] = True
def setMain(self, mainType, mainNode, exportName): callComment = getDocComment(mainNode) entry = self.main = { "type" : mainType, "name" : exportName, "line" : mainNode.line } if callComment: if callComment.text: html = callComment.getHtml(self.highlight) entry["doc"] = html entry["summary"] = Text.extractSummary(html) if hasattr(callComment, "tags"): entry["tags"] = callComment.tags if callComment is None or not callComment.text: entry["errornous"] = True self.warn('Missing comment on "%s" namespace' % exportName, mainNode.line)
def setMain(self, mainType, mainNode, exportName): callComment = getDocComment(mainNode) entry = self.main = { "type": mainType, "name": exportName, "line": mainNode.line } if callComment: if callComment.text: html = callComment.getHtml(self.highlight) entry["doc"] = html entry["summary"] = Text.extractSummary(html) if hasattr(callComment, "tags"): entry["tags"] = callComment.tags if callComment is None or not callComment.text: entry["errornous"] = True self.warn('Missing comment on "%s" namespace' % exportName, mainNode.line)
def addEntry(self, name, valueNode, commentNode, collection): # # Use already existing type or get type from node info # if name in collection: entry = collection[name] else: entry = collection[name] = { "type" : nodeTypeToDocType[valueNode.type] } # # Store generic data like line number and visibility # entry["line"] = valueNode.line entry["visibility"] = getVisibility(name) if name.upper() == name: entry["constant"] = True # # Complex structured types are processed in two steps # if entry["type"] == "Call" or entry["type"] == "Hook": commentNode = findCommentNode(commentNode) if commentNode: comment = getDocComment(commentNode) if comment: # Static type definition if comment.type: entry["type"] = comment.type self.addEntry(name, valueNode, commentNode, collection) return else: # Maybe type function: We need to ignore returns etc. which are often # the parent of the comment. funcValueNode = findFunction(commentNode) if funcValueNode: # Switch to function type for re-analysis entry["type"] = "Function" self.addEntry(name, funcValueNode, commentNode, collection) return if entry["type"] == "Call": callFunction = None if valueNode[0].type == "function": callFunction = valueNode[0] elif valueNode[0].type == "identifier": assignNodes, assignValues = findAssignments(valueNode[0].value, valueNode[0]) if assignNodes: callFunction = assignValues[0] if callFunction: # We try to analyze what the first return node returns returnNode = findReturn(callFunction) if returnNode and len(returnNode) > 0: returnValue = returnNode[0] entry["type"] = nodeTypeToDocType[returnValue.type] self.addEntry(name, returnValue, returnValue, collection) elif entry["type"] == "Hook": thenEntry = valueNode[1] thenType = nodeTypeToDocType[thenEntry.type] if not thenType in ("void", "null"): entry["type"] = thenType self.addEntry(name, thenEntry, thenEntry, collection) # Try second item for better data then null/void else: elseEntry = valueNode[2] elseType = nodeTypeToDocType[elseEntry.type] entry["type"] = elseType self.addEntry(name, elseEntry, elseEntry, collection) return # # Try to resolve identifiers # if entry["type"] == "Identifier": assignTypeNode, assignCommentNode = resolveIdentifierNode(valueNode) if assignTypeNode is not None: entry["type"] = nodeTypeToDocType[assignTypeNode.type] # Prefer comment from assignment, not from value if available self.addEntry(name, assignTypeNode, assignCommentNode, collection) return # # Processes special types: # # - Plus: Whether a string or number is created # - Object: Figures out the class instance which is created # if entry["type"] == "Plus": entry["type"] = detectPlusType(valueNode) elif entry["type"] == "Object": entry["type"] = detectObjectType(valueNode) # # Add human readable value # valueNodeHumanValue = valueToString(valueNode) if valueNodeHumanValue != entry["type"] and not valueNodeHumanValue in ("Other", "Call"): entry["value"] = valueNodeHumanValue # # Read data from comment and add documentation # comment = getDocComment(commentNode) if comment: if comment.tags: entry["tags"] = comment.tags if comment.type: entry["type"] = comment.type if comment.hasContent(): html = comment.getHtml(self.highlight) entry["doc"] = html entry["summary"] = Text.extractSummary(html) else: entry["errornous"] = True if comment.tags: entry["tags"] = comment.tags else: entry["errornous"] = True # # Add additional data for function types (params, returns) # if entry["type"] == "Function": # Add basic param data funcParams = getParamNamesFromFunction(valueNode) if funcParams: entry["params"] = {} for paramPos, paramName in enumerate(funcParams): entry["params"][paramName] = { "position" : paramPos } # Detect return type automatically returnNode = findReturn(valueNode) if returnNode and len(returnNode) > 0: autoReturnType = nodeTypeToDocType[returnNode[0].type] if autoReturnType == "Plus": autoReturnType = detectPlusType(returnNode[0]) elif autoReturnType in ("Call", "Object"): autoReturnType = "var" autoReturnEntry = { "name" : autoReturnType, "auto" : True } if autoReturnType in builtinTypes: autoReturnEntry["builtin"] = True if autoReturnType in pseudoTypes: autoReturnEntry["pseudo"] = True entry["returns"] = [autoReturnEntry] # Use comment for enrich existing data if comment: if comment.returns: entry["returns"] = comment.returns if funcParams: if not comment.params: for paramName in funcParams: entry["params"][paramName]["errornous"] = True else: for paramName in funcParams: if paramName in comment.params: entry["params"][paramName].update(comment.params[paramName]) else: entry["params"][paramName]["errornous"] = True
def addProperty(self, name, valueNode, commentNode, collection): entry = collection[name] = { "line": (commentNode or valueNode).line } comment = getDocComment(commentNode) if comment is None or not comment.text: entry["errornous"] = True self.warn('Missing or empty comment on property "%s"' % name, valueNode.line) else: html = comment.getHtml(self.highlight) entry["doc"] = html entry["summary"] = Text.extractSummary(html) if comment and comment.tags: entry["tags"] = comment.tags # Copy over value ptype = getKeyValue(valueNode, "type") if ptype and ptype.type == "string": entry["type"] = ptype.value pfire = getKeyValue(valueNode, "fire") if pfire and pfire.type == "string": entry["fire"] = pfire.value # Produce nice output for init value pinit = getKeyValue(valueNode, "init") if pinit: entry["init"] = valueToString(pinit) # Handle nullable, default value is true when an init value is there. Otherwise false. pnullable = getKeyValue(valueNode, "nullable") if pnullable: entry["nullable"] = pnullable.type == "true" elif pinit is not None and pinit.type != "null": entry["nullable"] = False else: entry["nullable"] = True # Just store whether an apply routine was defined papply = getKeyValue(valueNode, "apply") if papply and papply.type == "function": entry["apply"] = True # Multi Properties pthemeable = getKeyValue(valueNode, "themeable") if pthemeable and pthemeable.type == "true": entry["themeable"] = True pinheritable = getKeyValue(valueNode, "inheritable") if pinheritable and pinheritable.type == "true": entry["inheritable"] = True pgroup = getKeyValue(valueNode, "group") if pgroup and len(pgroup) > 0: entry["group"] = [child.value for child in pgroup] pshorthand = getKeyValue(valueNode, "shorthand") if pshorthand and pshorthand.type == "true": entry["shorthand"] = True
def addEntry(self, name, valueNode, commentNode, collection): # # Use already existing type or get type from node info # if name in collection: entry = collection[name] else: entry = collection[name] = { "type": nodeTypeToDocType[valueNode.type] } # # Store generic data like line number and visibility # entry["line"] = valueNode.line entry["visibility"] = getVisibility(name) if name.upper() == name: entry["constant"] = True # # Complex structured types are processed in two steps # if entry["type"] == "Call" or entry["type"] == "Hook": commentNode = findCommentNode(commentNode) if commentNode: comment = getDocComment(commentNode) if comment: # Static type definition if comment.type: entry["type"] = comment.type self.addEntry(name, valueNode, commentNode, collection) return else: # Maybe type function: We need to ignore returns etc. which are often # the parent of the comment. funcValueNode = findFunction(commentNode) if funcValueNode: # Switch to function type for re-analysis entry["type"] = "Function" self.addEntry(name, funcValueNode, commentNode, collection) return if entry["type"] == "Call": callFunction = None if valueNode[0].type == "function": callFunction = valueNode[0] elif valueNode[0].type == "identifier": assignNodes, assignValues = findAssignments( valueNode[0].value, valueNode[0]) if assignNodes: callFunction = assignValues[0] if callFunction: # We try to analyze what the first return node returns returnNode = findReturn(callFunction) if returnNode and len(returnNode) > 0: returnValue = returnNode[0] entry["type"] = nodeTypeToDocType[returnValue.type] self.addEntry(name, returnValue, returnValue, collection) elif entry["type"] == "Hook": thenEntry = valueNode[1] thenType = nodeTypeToDocType[thenEntry.type] if not thenType in ("void", "null"): entry["type"] = thenType self.addEntry(name, thenEntry, thenEntry, collection) # Try second item for better data then null/void else: elseEntry = valueNode[2] elseType = nodeTypeToDocType[elseEntry.type] entry["type"] = elseType self.addEntry(name, elseEntry, elseEntry, collection) return # # Try to resolve identifiers # if entry["type"] == "Identifier": assignTypeNode, assignCommentNode = resolveIdentifierNode( valueNode) if assignTypeNode is not None: entry["type"] = nodeTypeToDocType[assignTypeNode.type] # Prefer comment from assignment, not from value if available self.addEntry(name, assignTypeNode, assignCommentNode, collection) return # # Processes special types: # # - Plus: Whether a string or number is created # - Object: Figures out the class instance which is created # if entry["type"] == "Plus": entry["type"] = detectPlusType(valueNode) elif entry["type"] == "Object": entry["type"] = detectObjectType(valueNode) # # Add human readable value # valueNodeHumanValue = valueToString(valueNode) if valueNodeHumanValue != entry[ "type"] and not valueNodeHumanValue in ("Other", "Call"): entry["value"] = valueNodeHumanValue # # Read data from comment and add documentation # comment = getDocComment(commentNode) if comment: if comment.tags: entry["tags"] = comment.tags if comment.type: entry["type"] = comment.type if comment.hasContent(): html = comment.getHtml(self.highlight) entry["doc"] = html entry["summary"] = Text.extractSummary(html) else: entry["errornous"] = True if comment.tags: entry["tags"] = comment.tags else: entry["errornous"] = True # # Add additional data for function types (params, returns) # if entry["type"] == "Function": # Add basic param data funcParams = getParamNamesFromFunction(valueNode) if funcParams: entry["params"] = {} for paramPos, paramName in enumerate(funcParams): entry["params"][paramName] = {"position": paramPos} # Detect return type automatically returnNode = findReturn(valueNode) if returnNode and len(returnNode) > 0: autoReturnType = nodeTypeToDocType[returnNode[0].type] if autoReturnType == "Plus": autoReturnType = detectPlusType(returnNode[0]) elif autoReturnType in ("Call", "Object"): autoReturnType = "var" autoReturnEntry = {"name": autoReturnType, "auto": True} if autoReturnType in builtinTypes: autoReturnEntry["builtin"] = True if autoReturnType in pseudoTypes: autoReturnEntry["pseudo"] = True entry["returns"] = [autoReturnEntry] # Use comment for enrich existing data if comment: if comment.returns: entry["returns"] = comment.returns if funcParams: if not comment.params: for paramName in funcParams: entry["params"][paramName]["errornous"] = True else: for paramName in funcParams: if paramName in comment.params: entry["params"][paramName].update( comment.params[paramName]) else: entry["params"][paramName]["errornous"] = True
def addProperty(self, name, valueNode, commentNode, collection): entry = collection[name] = {"line": (commentNode or valueNode).line} comment = getDocComment(commentNode) if comment is None or not comment.text: entry["errornous"] = True self.warn('Missing or empty comment on property "%s"' % name, valueNode.line) else: html = comment.getHtml(self.highlight) entry["doc"] = html entry["summary"] = Text.extractSummary(html) if comment and comment.tags: entry["tags"] = comment.tags # Copy over value ptype = getKeyValue(valueNode, "type") if ptype and ptype.type == "string": entry["type"] = ptype.value pfire = getKeyValue(valueNode, "fire") if pfire and pfire.type == "string": entry["fire"] = pfire.value # Produce nice output for init value pinit = getKeyValue(valueNode, "init") if pinit: entry["init"] = valueToString(pinit) # Handle nullable, default value is true when an init value is there. Otherwise false. pnullable = getKeyValue(valueNode, "nullable") if pnullable: entry["nullable"] = pnullable.type == "true" elif pinit is not None and pinit.type != "null": entry["nullable"] = False else: entry["nullable"] = True # Just store whether an apply routine was defined papply = getKeyValue(valueNode, "apply") if papply and papply.type == "function": entry["apply"] = True # Multi Properties pthemeable = getKeyValue(valueNode, "themeable") if pthemeable and pthemeable.type == "true": entry["themeable"] = True pinheritable = getKeyValue(valueNode, "inheritable") if pinheritable and pinheritable.type == "true": entry["inheritable"] = True pgroup = getKeyValue(valueNode, "group") if pgroup and len(pgroup) > 0: entry["group"] = [child.value for child in pgroup] pshorthand = getKeyValue(valueNode, "shorthand") if pshorthand and pshorthand.type == "true": entry["shorthand"] = True
def __process(self, apiData, classFilter=None, internals=False, privates=False, printErrors=True, highlightCode=True): knownClasses = set(list(apiData)) # # Attaching Links to Source Code (Lines) # Building Documentation Summaries # Console.info("Adding Source Links...") for className in apiData: classApi = apiData[className] constructData = getattr(classApi, "construct", None) if constructData is not None: if "line" in constructData: constructData["sourceLink"] = "source:%s~%s" % (className, constructData["line"]) for section in ("properties", "events", "statics", "members"): sectionData = getattr(classApi, section, None) if sectionData is not None: for name in sectionData: if "line" in sectionData[name]: sectionData[name]["sourceLink"] = "source:%s~%s" % (className, sectionData[name]["line"]) # # Including Mixins / IncludedBy # Console.info("Resolving Mixins...") Console.indent() # Just used temporary to keep track of which classes are merged mergedClasses = set() def getApi(className): classApi = apiData[className] if className in mergedClasses: return classApi classIncludes = getattr(classApi, "includes", None) if classIncludes: for mixinName in classIncludes: if not mixinName in apiData: Console.error("Invalid mixin %s in class %s", className, mixinName) continue mixinApi = apiData[mixinName] if not hasattr(mixinApi, "includedBy"): mixinApi.includedBy = set() mixinApi.includedBy.add(className) mergeMixin(className, mixinName, classApi, getApi(mixinName)) mergedClasses.add(className) return classApi for className in apiData: apiData[className] = getApi(className) Console.outdent() # # Checking links # Console.info("Checking Links...") additionalTypes = ("Call", "Identifier", "Map", "Integer", "Node", "Element") def checkInternalLink(link, className): match = internalLinkParse.match(link) if not match: return 'Invalid link "#%s"' % link if match.group(3) is not None: className = match.group(3) if not className in knownClasses and not className in apiData: return 'Invalid class in link "#%s"' % link # Accept all section/item values for named classes, # as it might be pretty complicated to verify this here. if not className in apiData: return True classApi = apiData[className] sectionName = match.group(2) itemName = match.group(5) if itemName is None: return True if sectionName is not None: if not sectionName in linkMap: return 'Invalid section in link "#%s"' % link section = getattr(classApi, linkMap[sectionName], None) if section is None: return 'Invalid section in link "#%s"' % link else: if itemName in section: return True return 'Invalid item in link "#%s"' % link for sectionName in ("statics", "members", "properties", "events"): section = getattr(classApi, sectionName, None) if section and itemName in section: return True return 'Invalid item link "#%s"' % link def checkLinksInItem(item): # Process types if "type" in item: if item["type"] == "Function": # Check param types if "params" in item: for paramName in item["params"]: paramEntry = item["params"][paramName] if "type" in paramEntry: for paramTypeEntry in paramEntry["type"]: if not paramTypeEntry["name"] in knownClasses and not paramTypeEntry["name"] in additionalTypes and not ("builtin" in paramTypeEntry or "pseudo" in paramTypeEntry): item["errornous"] = True Console.error('Invalid param type "%s" in %s' % (paramTypeEntry["name"], className)) if not "pseudo" in paramTypeEntry and paramTypeEntry["name"] in knownClasses: paramTypeEntry["linkable"] = True # Check return types if "returns" in item: for returnTypeEntry in item["returns"]: if not returnTypeEntry["name"] in knownClasses and not returnTypeEntry["name"] in additionalTypes and not ("builtin" in returnTypeEntry or "pseudo" in returnTypeEntry): item["errornous"] = True Console.error('Invalid return type "%s" in %s' % (returnTypeEntry["name"], className)) if not "pseudo" in returnTypeEntry and returnTypeEntry["name"] in knownClasses: returnTypeEntry["linkable"] = True elif not item["type"] in builtinTypes and not item["type"] in pseudoTypes and not item["type"] in additionalTypes: item["errornous"] = True Console.error('Invalid type "%s" in %s' % (item["type"], className)) # Process doc if "doc" in item: def processInternalLink(match): linkUrl = match.group(2) if linkUrl.startswith("#"): linkCheck = checkInternalLink(linkUrl[1:], className) if linkCheck is not True: item["errornous"] = True if sectionName: Console.error("%s in %s:%s~%s" % (linkCheck, sectionName, className, name)) else: Console.error("%s in %s" % (linkCheck, className)) linkExtract.sub(processInternalLink, item["doc"]) Console.indent() # Process APIs for className in apiData: classApi = apiData[className] sectionName = None constructData = getattr(classApi, "construct", None) if constructData is not None: checkLinksInItem(constructData) for sectionName in ("properties", "events", "statics", "members"): section = getattr(classApi, sectionName, None) if section is not None: for name in section: checkLinksInItem(section[name]) Console.outdent() # # Filter Internals/Privates # Console.info("Filtering Items...") def isVisible(entry): if "visibility" in entry: visibility = entry["visibility"] if visibility == "private" and not privates: return False if visibility == "internal" and not internals: return False return True def filterInternalsPrivates(classApi, field): data = getattr(classApi, field, None) if data: for name in list(data): if not isVisible(data[name]): del data[name] for className in apiData: filterInternalsPrivates(apiData[className], "statics") filterInternalsPrivates(apiData[className], "members") # # Connection Interfaces / ImplementedBy # Console.info("Connecting Interfaces...") Console.indent() for className in apiData: classApi = getApi(className) if not hasattr(classApi, "main"): continue classType = classApi.main["type"] if classType == "core.Class": classImplements = getattr(classApi, "implements", None) if classImplements: for interfaceName in classImplements: interfaceApi = apiData[interfaceName] implementedBy = getattr(interfaceApi, "implementedBy", None) if not implementedBy: implementedBy = interfaceApi.implementedBy = [] implementedBy.append(className) connectInterface(className, interfaceName, classApi, interfaceApi) Console.outdent() # # Merging Named Classes # Console.info("Merging Named Classes...") Console.indent() for className in list(apiData): classApi = apiData[className] destName = classApi.main["name"] if destName is not None and destName != className: Console.debug("Extending class %s with %s", destName, className) if destName in apiData: destApi = apiData[destName] destApi.main["from"].append(className) else: destApi = apiData[destName] = Data.ApiData(destName, highlight=highlightCode) destApi.main = { "type" : "Extend", "name" : destName, "from" : [className] } # If there is a "main" tag found in the class use its API description if "tags" in classApi.main and classApi.main["tags"] is not None and "main" in classApi.main["tags"]: if "doc" in classApi.main: destApi.main["doc"] = classApi.main["doc"] classApi.main["extension"] = True # Read existing data construct = getattr(classApi, "construct", None) statics = getattr(classApi, "statics", None) members = getattr(classApi, "members", None) if construct is not None: if hasattr(destApi, "construct"): Console.warn("Overriding constructor in extension %s by %s", destName, className) destApi.construct = copy.copy(construct) if statics is not None: if not hasattr(destApi, "statics"): destApi.statics = {} for staticName in statics: destApi.statics[staticName] = copy.copy(statics[staticName]) destApi.statics[staticName]["from"] = className destApi.statics[staticName]["fromLink"] = "static:%s~%s" % (className, staticName) if members is not None: if not hasattr(destApi, "members"): destApi.members = {} for memberName in members: destApi.members[memberName] = copy.copy(members[memberName]) destApi.members[memberName]["from"] = className destApi.members[memberName]["fromLink"] = "member:%s~%s" % (className, memberName) Console.outdent() # # Connecting Uses / UsedBy # Console.info("Collecting Use Patterns...") # This matches all uses with the known classes and only keeps them if matched allClasses = set(list(apiData)) for className in apiData: uses = apiData[className].uses # Rebuild use list cleanUses = set() for use in uses: if use != className and use in allClasses: cleanUses.add(use) useEntry = apiData[use] if not hasattr(useEntry, "usedBy"): useEntry.usedBy = set() useEntry.usedBy.add(className) apiData[className].uses = cleanUses # # Collecting errors # Console.info("Collecting Errors...") Console.indent() for className in sorted(apiData): classApi = apiData[className] errors = [] if isErrornous(classApi.main): errors.append({ "kind": "Main", "name": None, "line": 1 }) if hasattr(classApi, "construct"): if isErrornous(classApi.construct): errors.append({ "kind": "Constructor", "name": None, "line": classApi.construct["line"] }) for section in ("statics", "members", "properties", "events"): items = getattr(classApi, section, {}) for itemName in items: item = items[itemName] if isErrornous(item): errors.append({ "kind": itemMap[section], "name": itemName, "line": item["line"] }) if errors: if printErrors: Console.warn("Found errors in %s", className) errorsSorted = sorted(errors, key=lambda entry: entry["line"]) if printErrors: Console.indent() for entry in errorsSorted: if entry["name"]: Console.warn("%s: %s (line %s)", entry["kind"], entry["name"], entry["line"]) else: Console.warn("%s (line %s)", entry["kind"], entry["line"]) Console.outdent() classApi.errors = errorsSorted Console.outdent() # # Building Search Index # Console.info("Building Search Index...") search = {} def addSearch(classApi, field): data = getattr(classApi, field, None) if data: for name in data: if not name in search: search[name] = set() search[name].add(className) for className in apiData: classApi = apiData[className] addSearch(classApi, "statics") addSearch(classApi, "members") addSearch(classApi, "properties") addSearch(classApi, "events") # # Post Process (dict to sorted list) # Console.info("Post Processing Data...") for className in sorted(apiData): classApi = apiData[className] convertTags(classApi.main) construct = getattr(classApi, "construct", None) if construct: convertFunction(construct) convertTags(construct) for section in ("statics", "members", "properties", "events"): items = getattr(classApi, section, None) if items: sortedList = [] for itemName in sorted(items): item = items[itemName] item["name"] = itemName if "type" in item and item["type"] == "Function": convertFunction(item) convertTags(item) sortedList.append(item) setattr(classApi, section, sortedList) # # Collecting Package Docs # Console.info("Collecting Package Docs...") Console.indent() # Inject existing package docs into api data for project in self.__session.getProjects(): docs = project.getDocs() for packageName in docs: if self.__isIncluded(packageName, classFilter): Console.debug("Creating package documentation %s", packageName) apiData[packageName] = docs[packageName].getApi() # Fill missing package docs for className in sorted(apiData): splits = className.split(".") packageName = splits[0] for split in splits[1:]: if not packageName in apiData: Console.warn("Missing package documentation %s", packageName) apiData[packageName] = Data.ApiData(packageName, highlight=highlightCode) apiData[packageName].main = { "type" : "Package", "name" : packageName } packageName = "%s.%s" % (packageName, split) # Now register all classes in their parent namespace/package for className in sorted(apiData): splits = className.split(".") packageName = ".".join(splits[:-1]) if packageName: package = apiData[packageName] # debug("- Registering class %s in parent %s", className, packageName) entry = { "name" : splits[-1], "link" : className, } classMain = apiData[className].main if "doc" in classMain and classMain["doc"]: summary = Text.extractSummary(classMain["doc"]) if summary: entry["summary"] = summary if "type" in classMain and classMain["type"]: entry["type"] = classMain["type"] if not hasattr(package, "content"): package.content = [entry] else: package.content.append(entry) Console.outdent() # # Writing API Index # Console.debug("Building Index...") index = {} for className in sorted(apiData): classApi = apiData[className] mainInfo = classApi.main # Create structure for className current = index for split in className.split("."): if not split in current: current[split] = {} current = current[split] # Store current type current["$type"] = mainInfo["type"] # Keep information if if hasattr(classApi, "content"): current["$content"] = True # # Return # return apiData, index, search
def __process(self, apiData, classFilter=None, internals=False, privates=False, printErrors=True, highlightCode=True): knownClasses = set(list(apiData)) # # Attaching Links to Source Code (Lines) # Building Documentation Summaries # Console.info("Adding Source Links...") for className in apiData: classApi = apiData[className] constructData = getattr(classApi, "construct", None) if constructData is not None: if "line" in constructData: constructData["sourceLink"] = "source:%s~%s" % ( className, constructData["line"]) for section in ("properties", "events", "statics", "members"): sectionData = getattr(classApi, section, None) if sectionData is not None: for name in sectionData: if "line" in sectionData[name]: sectionData[name][ "sourceLink"] = "source:%s~%s" % ( className, sectionData[name]["line"]) # # Including Mixins / IncludedBy # Console.info("Resolving Mixins...") Console.indent() # Just used temporary to keep track of which classes are merged mergedClasses = set() def getApi(className): classApi = apiData[className] if className in mergedClasses: return classApi classIncludes = getattr(classApi, "includes", None) if classIncludes: for mixinName in classIncludes: if not mixinName in apiData: Console.error("Invalid mixin %s in class %s", className, mixinName) continue mixinApi = apiData[mixinName] if not hasattr(mixinApi, "includedBy"): mixinApi.includedBy = set() mixinApi.includedBy.add(className) mergeMixin(className, mixinName, classApi, getApi(mixinName)) mergedClasses.add(className) return classApi for className in apiData: apiData[className] = getApi(className) Console.outdent() # # Checking links # Console.info("Checking Links...") additionalTypes = ("Call", "Identifier", "Map", "Integer", "Node", "Element") def checkInternalLink(link, className): match = internalLinkParse.match(link) if not match: return 'Invalid link "#%s"' % link if match.group(3) is not None: className = match.group(3) if not className in knownClasses and not className in apiData: return 'Invalid class in link "#%s"' % link # Accept all section/item values for named classes, # as it might be pretty complicated to verify this here. if not className in apiData: return True classApi = apiData[className] sectionName = match.group(2) itemName = match.group(5) if itemName is None: return True if sectionName is not None: if not sectionName in linkMap: return 'Invalid section in link "#%s"' % link section = getattr(classApi, linkMap[sectionName], None) if section is None: return 'Invalid section in link "#%s"' % link else: if itemName in section: return True return 'Invalid item in link "#%s"' % link for sectionName in ("statics", "members", "properties", "events"): section = getattr(classApi, sectionName, None) if section and itemName in section: return True return 'Invalid item link "#%s"' % link def checkLinksInItem(item): # Process types if "type" in item: if item["type"] == "Function": # Check param types if "params" in item: for paramName in item["params"]: paramEntry = item["params"][paramName] if "type" in paramEntry: for paramTypeEntry in paramEntry["type"]: if not paramTypeEntry[ "name"] in knownClasses and not paramTypeEntry[ "name"] in additionalTypes and not ( "builtin" in paramTypeEntry or "pseudo" in paramTypeEntry): item["errornous"] = True Console.error( 'Invalid param type "%s" in %s' % (paramTypeEntry["name"], className)) if not "pseudo" in paramTypeEntry and paramTypeEntry[ "name"] in knownClasses: paramTypeEntry["linkable"] = True # Check return types if "returns" in item: for returnTypeEntry in item["returns"]: if not returnTypeEntry[ "name"] in knownClasses and not returnTypeEntry[ "name"] in additionalTypes and not ( "builtin" in returnTypeEntry or "pseudo" in returnTypeEntry): item["errornous"] = True Console.error( 'Invalid return type "%s" in %s' % (returnTypeEntry["name"], className)) if not "pseudo" in returnTypeEntry and returnTypeEntry[ "name"] in knownClasses: returnTypeEntry["linkable"] = True elif not item["type"] in builtinTypes and not item[ "type"] in pseudoTypes and not item[ "type"] in additionalTypes: item["errornous"] = True Console.error('Invalid type "%s" in %s' % (item["type"], className)) # Process doc if "doc" in item: def processInternalLink(match): linkUrl = match.group(2) if linkUrl.startswith("#"): linkCheck = checkInternalLink(linkUrl[1:], className) if linkCheck is not True: item["errornous"] = True if sectionName: Console.error( "%s in %s:%s~%s" % (linkCheck, sectionName, className, name)) else: Console.error("%s in %s" % (linkCheck, className)) linkExtract.sub(processInternalLink, item["doc"]) Console.indent() # Process APIs for className in apiData: classApi = apiData[className] sectionName = None constructData = getattr(classApi, "construct", None) if constructData is not None: checkLinksInItem(constructData) for sectionName in ("properties", "events", "statics", "members"): section = getattr(classApi, sectionName, None) if section is not None: for name in section: checkLinksInItem(section[name]) Console.outdent() # # Filter Internals/Privates # Console.info("Filtering Items...") def isVisible(entry): if "visibility" in entry: visibility = entry["visibility"] if visibility == "private" and not privates: return False if visibility == "internal" and not internals: return False return True def filterInternalsPrivates(classApi, field): data = getattr(classApi, field, None) if data: for name in list(data): if not isVisible(data[name]): del data[name] for className in apiData: filterInternalsPrivates(apiData[className], "statics") filterInternalsPrivates(apiData[className], "members") # # Connection Interfaces / ImplementedBy # Console.info("Connecting Interfaces...") Console.indent() for className in apiData: classApi = getApi(className) if not hasattr(classApi, "main"): continue classType = classApi.main["type"] if classType == "core.Class": classImplements = getattr(classApi, "implements", None) if classImplements: for interfaceName in classImplements: interfaceApi = apiData[interfaceName] implementedBy = getattr(interfaceApi, "implementedBy", None) if not implementedBy: implementedBy = interfaceApi.implementedBy = [] implementedBy.append(className) connectInterface(className, interfaceName, classApi, interfaceApi) Console.outdent() # # Merging Named Classes # Console.info("Merging Named Classes...") Console.indent() for className in list(apiData): classApi = apiData[className] destName = classApi.main["name"] if destName is not None and destName != className: Console.debug("Extending class %s with %s", destName, className) if destName in apiData: destApi = apiData[destName] destApi.main["from"].append(className) else: destApi = apiData[destName] = Data.ApiData( destName, highlight=highlightCode) destApi.main = { "type": "Extend", "name": destName, "from": [className] } # If there is a "main" tag found in the class use its API description if "tags" in classApi.main and classApi.main[ "tags"] is not None and "main" in classApi.main["tags"]: if "doc" in classApi.main: destApi.main["doc"] = classApi.main["doc"] classApi.main["extension"] = True # Read existing data construct = getattr(classApi, "construct", None) statics = getattr(classApi, "statics", None) members = getattr(classApi, "members", None) if construct is not None: if hasattr(destApi, "construct"): Console.warn( "Overriding constructor in extension %s by %s", destName, className) destApi.construct = copy.copy(construct) if statics is not None: if not hasattr(destApi, "statics"): destApi.statics = {} for staticName in statics: destApi.statics[staticName] = copy.copy( statics[staticName]) destApi.statics[staticName]["from"] = className destApi.statics[staticName][ "fromLink"] = "static:%s~%s" % (className, staticName) if members is not None: if not hasattr(destApi, "members"): destApi.members = {} for memberName in members: destApi.members[memberName] = copy.copy( members[memberName]) destApi.members[memberName]["from"] = className destApi.members[memberName][ "fromLink"] = "member:%s~%s" % (className, memberName) Console.outdent() # # Connecting Uses / UsedBy # Console.info("Collecting Use Patterns...") # This matches all uses with the known classes and only keeps them if matched allClasses = set(list(apiData)) for className in apiData: uses = apiData[className].uses # Rebuild use list cleanUses = set() for use in uses: if use != className and use in allClasses: cleanUses.add(use) useEntry = apiData[use] if not hasattr(useEntry, "usedBy"): useEntry.usedBy = set() useEntry.usedBy.add(className) apiData[className].uses = cleanUses # # Collecting errors # Console.info("Collecting Errors...") Console.indent() for className in sorted(apiData): classApi = apiData[className] errors = [] if isErrornous(classApi.main): errors.append({"kind": "Main", "name": None, "line": 1}) if hasattr(classApi, "construct"): if isErrornous(classApi.construct): errors.append({ "kind": "Constructor", "name": None, "line": classApi.construct["line"] }) for section in ("statics", "members", "properties", "events"): items = getattr(classApi, section, {}) for itemName in items: item = items[itemName] if isErrornous(item): errors.append({ "kind": itemMap[section], "name": itemName, "line": item["line"] }) if errors: if printErrors: Console.warn("Found errors in %s", className) errorsSorted = sorted(errors, key=lambda entry: entry["line"]) if printErrors: Console.indent() for entry in errorsSorted: if entry["name"]: Console.warn("%s: %s (line %s)", entry["kind"], entry["name"], entry["line"]) else: Console.warn("%s (line %s)", entry["kind"], entry["line"]) Console.outdent() classApi.errors = errorsSorted Console.outdent() # # Building Search Index # Console.info("Building Search Index...") search = {} def addSearch(classApi, field): data = getattr(classApi, field, None) if data: for name in data: if not name in search: search[name] = set() search[name].add(className) for className in apiData: classApi = apiData[className] addSearch(classApi, "statics") addSearch(classApi, "members") addSearch(classApi, "properties") addSearch(classApi, "events") # # Post Process (dict to sorted list) # Console.info("Post Processing Data...") for className in sorted(apiData): classApi = apiData[className] convertTags(classApi.main) construct = getattr(classApi, "construct", None) if construct: convertFunction(construct) convertTags(construct) for section in ("statics", "members", "properties", "events"): items = getattr(classApi, section, None) if items: sortedList = [] for itemName in sorted(items): item = items[itemName] item["name"] = itemName if "type" in item and item["type"] == "Function": convertFunction(item) convertTags(item) sortedList.append(item) setattr(classApi, section, sortedList) # # Collecting Package Docs # Console.info("Collecting Package Docs...") Console.indent() # Inject existing package docs into api data for project in self.__session.getProjects(): docs = project.getDocs() for packageName in docs: if self.__isIncluded(packageName, classFilter): Console.debug("Creating package documentation %s", packageName) apiData[packageName] = docs[packageName].getApi() # Fill missing package docs for className in sorted(apiData): splits = className.split(".") packageName = splits[0] for split in splits[1:]: if not packageName in apiData: Console.warn("Missing package documentation %s", packageName) apiData[packageName] = Data.ApiData( packageName, highlight=highlightCode) apiData[packageName].main = { "type": "Package", "name": packageName } packageName = "%s.%s" % (packageName, split) # Now register all classes in their parent namespace/package for className in sorted(apiData): splits = className.split(".") packageName = ".".join(splits[:-1]) if packageName: package = apiData[packageName] # debug("- Registering class %s in parent %s", className, packageName) entry = { "name": splits[-1], "link": className, } classMain = apiData[className].main if "doc" in classMain and classMain["doc"]: summary = Text.extractSummary(classMain["doc"]) if summary: entry["summary"] = summary if "type" in classMain and classMain["type"]: entry["type"] = classMain["type"] if not hasattr(package, "content"): package.content = [entry] else: package.content.append(entry) Console.outdent() # # Writing API Index # Console.debug("Building Index...") index = {} for className in sorted(apiData): classApi = apiData[className] mainInfo = classApi.main # Create structure for className current = index for split in className.split("."): if not split in current: current[split] = {} current = current[split] # Store current type current["$type"] = mainInfo["type"] # Keep information if if hasattr(classApi, "content"): current["$content"] = True # # Return # return apiData, index, search