#!/usr/bin/env python import sys, string, re import config, tree, textile S_INLINE_COMMENT = "//.*" R_INLINE_COMMENT = re.compile("^" + S_INLINE_COMMENT + "$") R_INLINE_COMMENT_TIGHT = re.compile("^//\S+") R_INLINE_COMMENT_PURE = re.compile("^//") S_BLOCK_COMMENT = "/\*([^*]|[\n]|(\*+([^*/]|[\n])))*\*+/" R_BLOCK_COMMENT = re.compile("^" + S_BLOCK_COMMENT + "$") R_BLOCK_COMMENT_JAVADOC = re.compile("^/\*\*") R_BLOCK_COMMENT_QTDOC = re.compile("^/\*!") R_BLOCK_COMMENT_AREA = re.compile("^/\*\n\s*\*\*\*\*\*") R_BLOCK_COMMENT_DIVIDER = re.compile("^/\*\n\s*----") R_BLOCK_COMMENT_HEADER = re.compile("^/\* \*\*\*\*") R_BLOCK_COMMENT_TIGHT_START = re.compile("^/\*\S+") R_BLOCK_COMMENT_TIGHT_END = re.compile("\S+\*/$") R_BLOCK_COMMENT_PURE_START = re.compile("^/\*") R_BLOCK_COMMENT_PURE_END = re.compile("\*/$") R_ATTRIBUTE = re.compile(r'[^{]@(\w+)\s*') R_JAVADOC_STARS = re.compile(r'^\s*\*') R_NAMED_TYPE = re.compile(r'^\s*(\w+)\s*({([^}]+)})?') R_SIMPLE_TYPE = re.compile(r'^\s*({([^}]+)})?') VARPREFIXES = { "a" : "Array", "b" : "boolean", "d" : "Date", "f" : "Function", "i" : "int", "h" : "Map", "m" : "Map", "n" : "number", "o" : "Object", "r" : "RegExp", "s" : "string", "v" : "var", "w" : "Widget" } VARNAMES = { "a" : "Array", "arr" : "Array", "e" : "Event", "ev" : "Event", "evt" : "Event", "el" : "Element", "elem" : "Element", "elm" : "Element", "ex" : "Exception", "exc" : "Exception", "flag" : "boolean", "force" : "boolean", "f" : "Function", "func" : "Function", "h" : "Map", "hash" : "Map", "map" : "Map", "node" : "Node", "n" : "number", "num" : "number", "o" : "Object", "obj" : "Object", "reg" : "RegExp", "s" : "string", "str" : "string" } VARDESC = { "propValue" : "Current value", "propOldValue" : "Previous value", "propData" : "Property configuration map" } def outdent(source, indent): return re.compile("\n\s{%s}" % indent).sub("\n", source) def indent(source, indent): return re.compile("\n").sub("\n" + (" " * indent), source) def correctInline(source): if R_INLINE_COMMENT_TIGHT.match(source): return R_INLINE_COMMENT_PURE.sub("// ", source) return source def correctBlock(source): if not getFormat(source) in [ "javadoc", "qtdoc" ]: if R_BLOCK_COMMENT_TIGHT_START.search(source): source = R_BLOCK_COMMENT_PURE_START.sub("/* ", source) if R_BLOCK_COMMENT_TIGHT_END.search(source): source = R_BLOCK_COMMENT_PURE_END.sub(" */", source) return source def correct(source): if source.startswith("//"): return correctInline(source) else: return correctBlock(source) def isMultiLine(source): return source.find("\n") != -1 def getFormat(source): if R_BLOCK_COMMENT_JAVADOC.search(source): return "javadoc" elif R_BLOCK_COMMENT_QTDOC.search(source): return "qtdoc" elif R_BLOCK_COMMENT_AREA.search(source): return "area" elif R_BLOCK_COMMENT_DIVIDER.search(source): return "divider" elif R_BLOCK_COMMENT_HEADER.search(source): return "header" return "block" def hasThrows(node): if node.type == "throw": return True if node.hasChildren(): for child in node.children: if hasThrows(child): return True return False def getReturns(node, found): if node.type == "function": pass elif node.type == "return": if node.getChildrenLength(True) > 0: val = "var" else: val = "void" if node.hasChild("expression"): expr = node.getChild("expression") if expr.hasChild("variable"): var = expr.getChild("variable") if var.getChildrenLength(True) == 1 and var.hasChild("identifier"): val = nameToType(var.getChild("identifier").get("name")) else: val = "var" elif expr.hasChild("constant"): val = expr.getChild("constant").get("constantType") if val == "number": val = expr.getChild("constant").get("detail") elif expr.hasChild("array"): val = "Array" elif expr.hasChild("map"): val = "Map" elif expr.hasChild("function"): val = "Function" elif expr.hasChild("call"): val = "call" if not val in found: found.append(val) elif node.hasChildren(): for child in node.children: getReturns(child, found) return found def nameToType(name): typ = "var" # Evaluate type from name if name in VARNAMES: typ = VARNAMES[name] elif len(name) > 1: if name[1].isupper(): if name[0] in VARPREFIXES: typ = VARPREFIXES[name[0]] return typ def nameToDescription(name): desc = "TODOC" if name in VARDESC: desc = VARDESC[name] return desc def qt2javadoc(text): attribList = parseText(text, False) res = "/**" desc = getAttrib(attribList, "description")["text"] if "\n" in desc: res += "\n" for line in desc.split("\n"): res += " * %s\n" % line res += " " else: res += " %s " % desc res += "*/" return res def parseNode(node): """Takes the last doc comment from the commentsBefore child, parses it and returns a Node representing the doc comment""" # Find the last doc comment commentsBefore = node.getChild("commentsBefore", False) if commentsBefore and commentsBefore.hasChildren(): for child in commentsBefore.children: if child.type == "comment" and child.get("detail") in [ "javadoc", "qtdoc" ]: return parseText(child.get("text")) return [] def parseText(intext, format=True): # Strip "/**", "/*!" and "*/" intext = intext[3:-2] # Strip leading stars in every line text = "" for line in intext.split("\n"): text += R_JAVADOC_STARS.sub("", line).strip() + "\n" # Search for attributes desc = { "category" : "description", "text" : "" } attribs = [ desc ] pos = 0 while True: mtch = R_ATTRIBUTE.search(text, pos) if mtch == None: prevText = text[pos:].strip() if len(attribs) == 0: desc["text"] = prevText else: attribs[-1]["text"] = prevText break prevText = text[pos:mtch.start(0)].strip() pos = mtch.end(0) if len(attribs) == 0: desc["text"] = prevText else: attribs[-1]["text"] = prevText attribs.append({ "category" : mtch.group(1), "text" : "" }) # parse details for attrib in attribs: parseDetail(attrib, format) return attribs def parseDetail(attrib, format=True): text = attrib["text"] if attrib["category"] in [ "param", "event" ]: mtch = R_NAMED_TYPE.search(text) else: mtch = R_SIMPLE_TYPE.search(text) if mtch: text = text[mtch.end(0):] if attrib["category"] in [ "param", "event" ]: attrib["name"] = mtch.group(1) # print ">>> NAME: %s" % mtch.group(1) remain = mtch.group(3) else: remain = mtch.group(2) if remain != None: defIndex = remain.rfind("?") if defIndex != -1: attrib["default"] = remain[defIndex+1:].strip() remain = remain[0:defIndex].strip() # print ">>> DEFAULT: %s" % attrib["default"] typValues = [] for typ in remain.split("|"): typValue = typ.strip() arrayIndex = typValue.find("[") if arrayIndex != -1: arrayValue = (len(typValue) - arrayIndex) / 2 typValue = typValue[0:arrayIndex] else: arrayValue = 0 typValues.append({ "type" : typValue, "dimensions" : arrayValue }) if len(typValues) > 0: attrib["type"] = typValues # print ">>> TYPE: %s" % attrib["type"] if format: attrib["text"] = formatText(text) else: attrib["text"] = cleanupText(text) def cleanupText(text): #print "============= INTEXT =========================" #print text text = text.replace("

", "\n") text = text.replace("
", "\n") text = text.replace("
", "\n") text = text.replace("

", " ") newline = False lines = text.split("\n") text = "" for line in lines: line = line.strip() if line == "": if not newline: newline = True else: if text != "": text += "\n" if newline: text += "\n" newline = False text += line #print "============= OUTTEXT =========================" #print text return text def formatText(text): #print "============= FORMAT:1 =========================" #print text # cleanup HTML text = text.replace("

", "\n") text = text.replace("
", "\n") text = text.replace("
", "\n") text = text.replace("

", " ") # cleanup wraps text = text.replace("\n\n", "----BREAK----") text = text.replace("\n*", "----UL----") text = text.replace("\n#", "----OL----") text = text.replace("\n", " ") text = text.replace("----BREAK----", "\n\n") text = text.replace("----UL----", "\n*") text = text.replace("----OL----", "\n#") #print "============= FORMAT:2 =========================" #print text text = textile.textile(unicode(text).encode('utf-8')) #print "============= FORMAT:3 =========================" #print text return text def getAttrib(attribList, category): for attrib in attribList: if attrib["category"] == category: return attrib def getParam(attribList, name): for attrib in attribList: if attrib["category"] == "param": if attrib.has_key("name") and attrib["name"] == name: return attrib def attribHas(attrib, key): if attrib != None and attrib.has_key(key) and not attrib[key] in [ "", None ]: return True return False def splitText(orig, attrib=True): res = "" first = True for line in orig.split("\n"): if attrib: if first: res += " %s\n" % line else: res += " * %s\n" % line else: res += " * %s\n" % line first = False if not res.endswith("\n"): res += "\n" return res def parseType(vtype): typeText = "" firstType = True for entry in vtype: if not firstType: typeText += " | " typeText += entry["type"] if entry.has_key("dimensions") and entry["dimensions"] > 0: typeText += "[]" * entry["dimensions"] firstType = False return typeText def fromNode(node, assignType, name, alternative, old=[]): # # description ############################################################## oldDesc = getAttrib(old, "description") if attribHas(oldDesc, "text"): newText = oldDesc["text"] else: newText = "{var} TODOC" if "\n" in newText: s = "/**\n%s\n-*/" % splitText(newText, False) else: s = "/** %s */" % newText # # other @attributes ############################################################## for attrib in old: cat = attrib["category"] if cat != "description": print " * Found unallowed attribute %s in comment for %s" % (cat, name) return s def fromFunction(func, assignType, name, alternative, old=[]): # # open comment ############################################################## s = "/**\n" # # description ############################################################## oldDesc = getAttrib(old, "description") if attribHas(oldDesc, "text"): newText = oldDesc["text"] else: newText = "TODOC" s += splitText(newText, False) s += " *\n" # # add @type ############################################################## if assignType != None: s += " * @type %s\n" % assignType else: s += " * @type unknown TODOC\n" # # add @name ############################################################## if name != None and name != "construct": s += " * @name %s\n" % name if name.startswith("__"): s += " * @access private\n" elif name.startswith("_"): s += " * @access protected\n" else: s += " * @access public\n" # # add @alternative ############################################################## oldAlternative = getAttrib(old, "alternative") if alternative: if attribHas(oldAlternative, "text"): newText = oldDesc["text"] else: newText = "TODOC" s += " * @alternative%s" % splitText(newText) if not s.endswith("\n"): s += "\n" elif oldAlternative: print " * Removing old @alternative for %s" % name # # add @abstract ############################################################## oldAbstract = getAttrib(old, "abstract") first = func.getChild("body").getChild("block").getFirstChild(False, True) abstract = first and first.type == "throw" if abstract: if attribHas(oldAbstract, "text"): newText = oldDesc["text"] else: newText = "" s += " * @abstract%s" % splitText(newText) if not s.endswith("\n"): s += "\n" elif oldAbstract: print " * Removing old @abstract for %s" % name # # add @param ############################################################## params = func.getChild("params") if params.hasChildren(): for child in params.children: if child.type == "variable": newName = child.getChild("identifier").get("name") newType = newTypeText = nameToType(newName) newDefault = "" newText = nameToDescription(newName) oldParam = getParam(old, newName) # Get type and text from old content if oldParam: if attribHas(oldParam, "type"): newTypeText = parseType(oldParam["type"]) if attribHas(oldParam, "defaultValue"): newDefault = oldParam["defaultValue"] if attribHas(oldParam, "text"): newText = oldParam["text"].strip() s += " * @param %s {%s%s}%s" % (newName, newTypeText, newDefault, splitText(newText)) if not s.endswith("\n"): s += "\n" # # add @return ############################################################## if name != "construct": oldReturn = getAttrib(old, "return") newType = "void" newText = "" # Get type and text from old content if oldReturn: if attribHas(oldReturn, "type"): newType = parseType(oldReturn["type"]) if attribHas(oldReturn, "text"): newText = oldReturn["text"].strip() # Try to autodetect the type if newType == "void": returns = getReturns(func.getChild("body"), []) if len(returns) > 0: newType = " | ".join(returns) elif name != None and name.startswith("is") and name[3].isupper(): newType = "boolean" # Add documentation hint in non void cases if newType != "void" and newText == "": newText = "TODOC" s += " * @return {%s}%s" % (newType, splitText(newText)) if not s.endswith("\n"): s += "\n" # # add @throws ############################################################## oldThrows = getAttrib(old, "throws") if hasThrows(func): if oldThrows and attribHas(oldThrows, "text"): newText = oldThrows["text"] elif abstract: newText = "the abstract function warning." else: newText = "TODOC" s += " * @throws%s" % splitText(newText) if not s.endswith("\n"): s += "\n" elif oldThrows: print " * Removing old @throw attribute in comment for %s" % name # # other @attributes ############################################################## for attrib in old: cat = attrib["category"] if cat in [ "see", "author", "deprecated", "exception", "since", "version", "abstract", "overridden" ]: s += " * @%s" % cat if attribHas(attrib, "text"): s += splitText(attrib["text"]) if not s.endswith("\n"): s += "\n" elif not cat in [ "name", "access", "membership", "alternative", "param", "return", "throws", "description" ]: print " * Found unallowed attribute %s in comment for %s" % (cat, name) # # close comment ############################################################## s += " */" return s def fill(node): if node.type in [ "comment", "commentsBefore", "commentsAfter" ]: return if node.hasParent(): target = node if node.type == "function": name = node.get("name", False) else: name = "" alternative = False assignType = None if name != None: assignType = "function" # move to hook operation while target.parent.type in [ "first", "second", "third" ] and target.parent.parent.type == "operation" and target.parent.parent.get("operator") == "HOOK": alternative = True target = target.parent.parent # move comment to assignment while target.parent.type == "right" and target.parent.parent.type == "assignment": target = target.parent.parent if target.hasChild("left"): left = target.getChild("left") if left and left.hasChild("variable"): var = left.getChild("variable") last = var.getLastChild(False, True) if last and last.type == "identifier": name = last.get("name") assignType = "object" for child in var.children: if child.type == "identifier": if child.get("name") in [ "prototype", "Proto" ]: assignType = "member" elif child.get("name") in [ "class", "base", "Class" ]: assignType = "static" elif target.parent.type == "definition": name = target.parent.get("identifier") assignType = "definition" # move to definition if target.parent.type == "assignment" and target.parent.parent.type == "definition" and target.parent.parent.parent.getChildrenLength(True) == 1: target = target.parent.parent.parent assignType = "function" # move comment to keyvalue if target.parent.type == "value" and target.parent.parent.type == "keyvalue": target = target.parent.parent name = target.get("key") assignType = "map" if name == "construct": assignType = "constructor" if target.parent.type == "map" and target.parent.parent.type == "value" and target.parent.parent.parent.type == "keyvalue": paname = target.parent.parent.parent.get("key") if paname == "members": assignType = "member" elif paname == "statics": assignType = "static" # filter stuff, only add comments to member and static values and to all functions if assignType in [ "member", "static" ] or node.type == "function": if not hasattr(target, "documentationAdded") and target.parent.type != "params": old = [] # create commentsBefore if target.hasChild("commentsBefore"): commentsBefore = target.getChild("commentsBefore") if commentsBefore.hasChild("comment"): for child in commentsBefore.children: if child.get("detail") in [ "javadoc", "qtdoc" ]: old = parseText(child.get("text"), False) commentsBefore.removeChild(child) break else: commentsBefore = tree.Node("commentsBefore") target.addChild(commentsBefore) # create comment node commentNode = tree.Node("comment") if node.type == "function": commentNode.set("text", fromFunction(node, assignType, name, alternative, old)) else: commentNode.set("text", fromNode(node, assignType, name, alternative, old)) commentNode.set("detail", "javadoc") commentNode.set("multiline", True) commentsBefore.addChild(commentNode) # in case of alternative methods, use the first one, ignore the others target.documentationAdded = True if node.hasChildren(): for child in node.children: fill(child)