diff -r 3fc8dfeb6ebe -r b99e7fd55fd3 src/eric7/DocumentationTools/ModuleDocumentor.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/eric7/DocumentationTools/ModuleDocumentor.py Thu Jul 07 11:23:56 2022 +0200 @@ -0,0 +1,1148 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2003 - 2022 Detlev Offenbach <detlev@die-offenbachs.de> +# + +""" +Module implementing the builtin documentation generator. + +The different parts of the module document are assembled from the parsed +Python file. The appearance is determined by several templates defined within +this module. +""" + +import sys +import re +import contextlib + +from Utilities import html_uencode +from Utilities.ModuleParser import RB_SOURCE, Function + +from . import TemplatesListsStyleCSS + +_signal = re.compile( + r""" + ^@signal [ \t]+ + (?P<SignalName1> + [a-zA-Z_] \w* [ \t]* \( [^)]* \) + ) + [ \t]* (?P<SignalDescription1> .*) +| + ^@signal [ \t]+ + (?P<SignalName2> + [a-zA-Z_] \w* + ) + [ \t]+ (?P<SignalDescription2> .*) + """, re.VERBOSE | re.DOTALL | re.MULTILINE).search + +_event = re.compile( + r""" + ^@event [ \t]+ + (?P<EventName1> + [a-zA-Z_] \w* [ \t]* \( [^)]* \) + ) + [ \t]* (?P<EventDescription1> .*) +| + ^@event [ \t]+ + (?P<EventName2> + [a-zA-Z_] \w* + ) + [ \t]+ (?P<EventDescription2> .*) + """, re.VERBOSE | re.DOTALL | re.MULTILINE).search + + +class TagError(Exception): + """ + Exception class raised, if an invalid documentation tag was found. + """ + pass + + +class ModuleDocument: + """ + Class implementing the builtin documentation generator. + """ + def __init__(self, module): + """ + Constructor + + @param module information of the parsed Python file + @type str + """ + self.module = module + self.empty = True + + self.keywords = [] + # list of tuples containing the name (string) and + # the ref (string). The ref is without the filename part. + self.generated = False + + def isEmpty(self): + """ + Public method to determine, if the module contains any classes or + functions. + + @return Flag indicating an empty module (i.e. __init__.py without + any contents) + """ + return self.empty + + def name(self): + """ + Public method used to get the module name. + + @return The name of the module. (string) + """ + return self.module.name + + def description(self): + """ + Public method used to get the description of the module. + + @return The description of the module. (string) + """ + return self.__formatDescription(self.module.description) + + def shortDescription(self): + """ + Public method used to get the short description of the module. + + The short description is just the first line of the modules + description. + + @return The short description of the module. (string) + """ + return self.__getShortDescription(self.module.description) + + def genDocument(self): + """ + Public method to generate the source code documentation. + + @return The source code documentation. (string) + """ + doc = ( + TemplatesListsStyleCSS.headerTemplate.format( + **{'Title': self.module.name} + ) + + self.__genModuleSection() + + TemplatesListsStyleCSS.footerTemplate + ) + self.generated = True + return doc + + def __genModuleSection(self): + """ + Private method to generate the body of the document. + + @return The body of the document. (string) + """ + globalsList = self.__genGlobalsListSection() + classList = self.__genClassListSection() + functionList = self.__genFunctionListSection() + try: + if self.module.type == RB_SOURCE: + rbModulesList = self.__genRbModulesListSection() + modBody = TemplatesListsStyleCSS.rbFileTemplate.format( + **{'Module': self.module.name, + 'ModuleDescription': + self.__formatDescription(self.module.description), + 'GlobalsList': globalsList, + 'ClassList': classList, + 'RbModulesList': rbModulesList, + 'FunctionList': functionList, + }) + else: + modBody = TemplatesListsStyleCSS.moduleTemplate.format( + **{'Module': self.module.name, + 'ModuleDescription': + self.__formatDescription(self.module.description), + 'GlobalsList': globalsList, + 'ClassList': classList, + 'FunctionList': functionList, + }) + except TagError as e: + sys.stderr.write( + "Error processing {0}.\n".format(self.module.file)) + sys.stderr.write( + "Error in tags of description of module {0}.\n".format( + self.module.name)) + sys.stderr.write("{0}\n".format(e)) + return "" + + classesSection = self.__genClassesSection() + functionsSection = self.__genFunctionsSection() + rbModulesSection = ( + self.__genRbModulesSection() + if self.module.type == RB_SOURCE else + "" + ) + return "{0}{1}{2}{3}".format( + modBody, classesSection, rbModulesSection, functionsSection) + + def __genListSection(self, names, sectionDict, kwSuffix=""): + """ + Private method to generate a list section of the document. + + @param names The names to appear in the list. (list of strings) + @param sectionDict dictionary containing all relevant information + (dict) + @param kwSuffix suffix to be used for the QtHelp keywords (string) + @return list section (string) + """ + lst = [] + for name in names: + lst.append(TemplatesListsStyleCSS.listEntryTemplate.format( + **{'Link': "{0}".format(name), + 'Name': sectionDict[name].name, + 'Description': + self.__getShortDescription(sectionDict[name].description), + 'Deprecated': + self.__checkDeprecated(sectionDict[name].description) and + TemplatesListsStyleCSS.listEntryDeprecatedTemplate or "", + })) + n = ("{0} ({1})".format(name, kwSuffix) if kwSuffix + else "{0}".format(name)) + self.keywords.append((n, "#{0}".format(name))) + return ''.join(lst) + + def __genGlobalsListSection(self, class_=None): + """ + Private method to generate the section listing all global attributes of + the module. + + @param class_ reference to a class object (Class) + @return The globals list section. (string) + """ + attrNames = [] + scope = class_ if class_ is not None else self.module + attrNames = sorted(attr for attr in scope.globals.keys() + if not scope.globals[attr].isSignal) + s = ( + ''.join( + [TemplatesListsStyleCSS.listEntrySimpleTemplate + .format(**{'Name': name}) for name in attrNames]) + if attrNames else + TemplatesListsStyleCSS.listEntryNoneTemplate + ) + return TemplatesListsStyleCSS.listTemplate.format(**{'Entries': s}) + + def __genClassListSection(self): + """ + Private method to generate the section listing all classes of the + module. + + @return The classes list section. (string) + """ + names = sorted(self.module.classes.keys()) + if names: + self.empty = False + s = self.__genListSection(names, self.module.classes) + else: + s = TemplatesListsStyleCSS.listEntryNoneTemplate + return TemplatesListsStyleCSS.listTemplate.format(**{'Entries': s}) + + def __genRbModulesListSection(self): + """ + Private method to generate the section listing all modules of the file + (Ruby only). + + @return The modules list section. (string) + """ + names = sorted(self.module.modules.keys()) + if names: + self.empty = False + s = self.__genListSection(names, self.module.modules) + else: + s = TemplatesListsStyleCSS.listEntryNoneTemplate + return TemplatesListsStyleCSS.listTemplate.format(**{'Entries': s}) + + def __genFunctionListSection(self): + """ + Private method to generate the section listing all functions of the + module. + + @return The functions list section. (string) + """ + names = sorted(self.module.functions.keys()) + if names: + self.empty = False + s = self.__genListSection(names, self.module.functions) + else: + s = TemplatesListsStyleCSS.listEntryNoneTemplate + return TemplatesListsStyleCSS.listTemplate.format(**{'Entries': s}) + + def __genClassesSection(self): + """ + Private method to generate the document section with details about + classes. + + @return The classes details section. (string) + """ + classNames = sorted(self.module.classes.keys()) + classes = [] + for className in classNames: + _class = self.module.classes[className] + supers = _class.super + supers = ', '.join(supers) if len(supers) > 0 else "None" + + globalsList = self.__genGlobalsListSection(_class) + classMethList, classMethBodies = self.__genMethodSection( + _class, className, Function.Class) + methList, methBodies = self.__genMethodSection( + _class, className, Function.General) + staticMethList, staticMethBodies = self.__genMethodSection( + _class, className, Function.Static) + + try: + clsBody = TemplatesListsStyleCSS.classTemplate.format( + **{'Anchor': className, + 'Class': _class.name, + 'ClassSuper': supers, + 'ClassDescription': + self.__formatDescription(_class.description), + 'GlobalsList': globalsList, + 'ClassMethodList': classMethList, + 'MethodList': methList, + 'StaticMethodList': staticMethList, + 'MethodDetails': + classMethBodies + methBodies + staticMethBodies, + }) + except TagError as e: + sys.stderr.write( + "Error processing {0}.\n".format(self.module.file)) + sys.stderr.write( + "Error in tags of description of class {0}.\n".format( + className)) + sys.stderr.write("{0}\n".format(e)) + clsBody = "" + + classes.append(clsBody) + + return ''.join(classes) + + def __genMethodsListSection(self, names, sectionDict, className, clsName, + includeInit=True): + """ + Private method to generate the methods list section of a class. + + @param names names to appear in the list (list of strings) + @param sectionDict dictionary containing all relevant information + (dict) + @param className class name containing the names + @param clsName visible class name containing the names + @param includeInit flag indicating to include the __init__ method + (boolean) + @return methods list section (string) + """ + lst = [] + if includeInit: + with contextlib.suppress(KeyError): + lst.append(TemplatesListsStyleCSS.listEntryTemplate.format( + **{'Link': "{0}.{1}".format(className, '__init__'), + 'Name': clsName, + 'Description': self.__getShortDescription( + sectionDict['__init__'].description), + 'Deprecated': self.__checkDeprecated( + sectionDict['__init__'].description) and + TemplatesListsStyleCSS.listEntryDeprecatedTemplate or + "", + })) + self.keywords.append( + ("{0} (Constructor)".format(className), + "#{0}.{1}".format(className, '__init__'))) + + for name in names: + lst.append(TemplatesListsStyleCSS.listEntryTemplate.format( + **{'Link': "{0}.{1}".format(className, name), + 'Name': sectionDict[name].name, + 'Description': + self.__getShortDescription(sectionDict[name].description), + 'Deprecated': + self.__checkDeprecated(sectionDict[name].description) and + TemplatesListsStyleCSS.listEntryDeprecatedTemplate or "", + })) + self.keywords.append(("{0}.{1}".format(className, name), + "#{0}.{1}".format(className, name))) + return ''.join(lst) + + def __genMethodSection(self, obj, className, modifierFilter): + """ + Private method to generate the method details section. + + @param obj reference to the object being formatted + @param className name of the class containing the method (string) + @param modifierFilter filter value designating the method types + @return method list and method details section (tuple of two string) + """ + methList = [] + methBodies = [] + methods = sorted(k for k in obj.methods.keys() + if obj.methods[k].modifier == modifierFilter) + if '__init__' in methods: + methods.remove('__init__') + try: + methBody = TemplatesListsStyleCSS.constructorTemplate.format( + **{'Anchor': className, + 'Class': obj.name, + 'Method': '__init__', + 'MethodDescription': + self.__formatDescription( + obj.methods['__init__'].description), + 'Params': + ', '.join(obj.methods['__init__'].parameters[1:]), + }) + except TagError as e: + sys.stderr.write( + "Error processing {0}.\n".format(self.module.file)) + sys.stderr.write( + "Error in tags of description of method {0}.{1}.\n".format( + className, '__init__')) + sys.stderr.write("{0}\n".format(e)) + methBody = "" + methBodies.append(methBody) + + if modifierFilter == Function.Class: + methodClassifier = " (class method)" + elif modifierFilter == Function.Static: + methodClassifier = " (static)" + else: + methodClassifier = "" + for method in methods: + try: + methBody = TemplatesListsStyleCSS.methodTemplate.format( + **{'Anchor': className, + 'Class': obj.name, + 'Method': obj.methods[method].name, + 'MethodClassifier': methodClassifier, + 'MethodDescription': + self.__formatDescription( + obj.methods[method].description), + 'Params': ', '.join(obj.methods[method].parameters[1:]), + }) + except TagError as e: + sys.stderr.write( + "Error processing {0}.\n".format(self.module.file)) + sys.stderr.write( + "Error in tags of description of method {0}.{1}.\n".format( + className, method)) + sys.stderr.write("{0}\n".format(e)) + methBody = "" + methBodies.append(methBody) + + methList = self.__genMethodsListSection( + methods, obj.methods, className, obj.name, + includeInit=modifierFilter == Function.General) + + if not methList: + methList = TemplatesListsStyleCSS.listEntryNoneTemplate + return (TemplatesListsStyleCSS.listTemplate + .format(**{'Entries': methList}), ''.join(methBodies)) + + def __genRbModulesSection(self): + """ + Private method to generate the document section with details about + Ruby modules. + + @return The Ruby modules details section. (string) + """ + rbModulesNames = sorted(self.module.modules.keys()) + rbModules = [] + for rbModuleName in rbModulesNames: + rbModule = self.module.modules[rbModuleName] + globalsList = self.__genGlobalsListSection(rbModule) + methList, methBodies = self.__genMethodSection( + rbModule, rbModuleName, Function.General) + classList, classBodies = self.__genRbModulesClassesSection( + rbModule, rbModuleName) + + try: + rbmBody = TemplatesListsStyleCSS.rbModuleTemplate.format( + **{'Anchor': rbModuleName, + 'Module': rbModule.name, + 'ModuleDescription': + self.__formatDescription(rbModule.description), + 'GlobalsList': globalsList, + 'ClassesList': classList, + 'ClassesDetails': classBodies, + 'FunctionsList': methList, + 'FunctionsDetails': methBodies, + }) + except TagError as e: + sys.stderr.write( + "Error processing {0}.\n".format(self.module.file)) + sys.stderr.write( + "Error in tags of description of Ruby module {0}.\n" + .format(rbModuleName)) + sys.stderr.write("{0}\n".format(e)) + rbmBody = "" + + rbModules.append(rbmBody) + + return ''.join(rbModules) + + def __genRbModulesClassesSection(self, obj, modName): + """ + Private method to generate the Ruby module classes details section. + + @param obj Reference to the object being formatted. + @param modName Name of the Ruby module containing the classes. (string) + @return The classes list and classes details section. + (tuple of two string) + """ + classNames = sorted(obj.classes.keys()) + classes = [] + for className in classNames: + _class = obj.classes[className] + supers = _class.super + supers = ', '.join(supers) if len(supers) > 0 else "None" + + methList, methBodies = self.__genMethodSection( + _class, className, Function.General) + + try: + clsBody = TemplatesListsStyleCSS.rbModulesClassTemplate.format( + **{'Anchor': className, + 'Class': _class.name, + 'ClassSuper': supers, + 'ClassDescription': + self.__formatDescription(_class.description), + 'MethodList': methList, + 'MethodDetails': methBodies, + }) + except TagError as e: + sys.stderr.write( + "Error processing {0}.\n".format(self.module.file)) + sys.stderr.write( + "Error in tags of description of class {0}.\n".format( + className)) + sys.stderr.write("{0}\n".format(e)) + clsBody = "" + + classes.append(clsBody) + + classesList = self.__genRbModulesClassesListSection( + classNames, obj.classes, modName) + + if not classesList: + classesList = TemplatesListsStyleCSS.listEntryNoneTemplate + return (TemplatesListsStyleCSS.listTemplate + .format(**{'Entries': classesList}), ''.join(classes)) + + def __genRbModulesClassesListSection(self, names, sectionDict, moduleName): + """ + Private method to generate the classes list section of a Ruby module. + + @param names The names to appear in the list. (list of strings) + @param sectionDict dictionary containing all relevant information + (dict) + @param moduleName name of the Ruby module containing the classes + (string) + @return The list section. (string) + """ + lst = [] + for name in names: + lst.append(TemplatesListsStyleCSS.listEntryTemplate.format( + **{'Link': "{0}.{1}".format(moduleName, name), + 'Name': sectionDict[name].name, + 'Description': + self.__getShortDescription(sectionDict[name].description), + 'Deprecated': + self.__checkDeprecated(sectionDict[name].description) and + TemplatesListsStyleCSS.listEntryDeprecatedTemplate or "", + })) + self.keywords.append(("{0}.{1}".format(moduleName, name), + "#{0}.{1}".format(moduleName, name))) + return ''.join(lst) + + def __genFunctionsSection(self): + """ + Private method to generate the document section with details about + functions. + + @return The functions details section. (string) + """ + funcBodies = [] + funcNames = sorted(self.module.functions.keys()) + for funcName in funcNames: + try: + funcBody = TemplatesListsStyleCSS.functionTemplate.format( + **{'Anchor': funcName, + 'Function': self.module.functions[funcName].name, + 'FunctionDescription': self.__formatDescription( + self.module.functions[funcName].description), + 'Params': + ', '.join(self.module.functions[funcName].parameters), + }) + except TagError as e: + sys.stderr.write( + "Error processing {0}.\n".format(self.module.file)) + sys.stderr.write( + "Error in tags of description of function {0}.\n".format( + funcName)) + sys.stderr.write("{0}\n".format(e)) + funcBody = "" + + funcBodies.append(funcBody) + + return ''.join(funcBodies) + + def __getShortDescription(self, desc): + """ + Private method to determine the short description of an object. + + The short description is just the first non empty line of the + documentation string. + + @param desc The documentation string. (string) + @return The short description. (string) + """ + dlist = desc.splitlines() + sdlist = [] + descfound = 0 + for desc in dlist: + desc = desc.strip() + if desc: + descfound = 1 + dotpos = desc.find('.') + if dotpos == -1: + sdlist.append(desc.strip()) + else: + while ( + dotpos + 1 < len(desc) and + not desc[dotpos + 1].isspace() + ): + # don't recognize '.' inside a number or word as + # stop condition + dotpos = desc.find('.', dotpos + 1) + if dotpos == -1: + break + if dotpos == -1: + sdlist.append(desc.strip()) + else: + sdlist.append(desc[:dotpos + 1].strip()) + break # break if a '.' is found + else: + if descfound: + break # break if an empty line is found + if sdlist: + return html_uencode(' '.join(sdlist)) + else: + return '' + + def __checkDeprecated(self, descr): + """ + Private method to check, if the object to be documented contains a + deprecated flag. + + @param descr documentation string (string) + @return flag indicating the deprecation status (boolean) + """ + dlist = descr.splitlines() + for desc in dlist: + desc = desc.strip() + if desc.startswith("@deprecated"): + return True + return False + + def __genParagraphs(self, lines): + """ + Private method to assemble the descriptive paragraphs of a docstring. + + A paragraph is made up of a number of consecutive lines without + an intermediate empty line. Empty lines are treated as a paragraph + delimiter. + + @param lines A list of individual lines. (list of strings) + @return Ready formatted paragraphs. (string) + """ + lst = [] + linelist = [] + for line in lines: + if line.strip(): + if line == '.': + linelist.append("") + else: + linelist.append(html_uencode(line)) + else: + lst.append(TemplatesListsStyleCSS.paragraphTemplate.format( + **{'Lines': '\n'.join(linelist)})) + linelist = [] + if linelist: + lst.append(TemplatesListsStyleCSS.paragraphTemplate.format( + **{'Lines': '\n'.join(linelist)})) + return ''.join(lst) + + def __genDescriptionListSection(self, dictionary, template): + """ + Private method to generate the list section of a description. + + @param dictionary Dictionary containing the info for the + list section. + @param template The template to be used for the list. (string) + @return The list section. (string) + """ + lst = [] + keys = sorted(dictionary.keys()) + for key in keys: + lst.append(template.format( + **{'Name': key, + 'Description': html_uencode('\n'.join(dictionary[key])), + })) + return ''.join(lst) + + def __genParamDescriptionListSection(self, _list): + """ + Private method to generate the list section of a description. + + @param _list list containing the info for the parameter description + list section (list of lists with three elements) + @return formatted list section (string) + """ + lst = [] + for name, type_, lines in _list: + if type_: + lst.append( + TemplatesListsStyleCSS.parameterTypesListEntryTemplate + .format( + **{'Name': name, + 'Type': type_, + 'Description': html_uencode('\n'.join(lines)), + } + ) + ) + else: + lst.append( + TemplatesListsStyleCSS.parametersListEntryTemplate.format( + **{'Name': name, + 'Description': html_uencode('\n'.join(lines)), + } + ) + ) + return ''.join(lst) + + def __formatCrossReferenceEntry(self, entry): + """ + Private method to format a cross reference entry. + + This cross reference entry looks like "package.module#member label". + + @param entry the entry to be formatted (string) + @return formatted entry (string) + """ + if entry.startswith('"'): + return entry + elif entry.startswith('<'): + entry = entry[3:] + else: + try: + reference, label = entry.split(None, 1) + except ValueError: + reference = entry + label = entry + try: + path, anchor = reference.split('#', 1) + except ValueError: + path = reference + anchor = '' + reference = path and "{0}.html".format(path) or '' + if anchor: + reference = "{0}#{1}".format(reference, anchor) + entry = 'href="{0}">{1}</a>'.format(reference, label) + + return TemplatesListsStyleCSS.seeLinkTemplate.format(**{'Link': entry}) + + def __genSeeListSection(self, _list, template): + """ + Private method to generate the "see also" list section of a + description. + + @param _list List containing the info for the section. + @param template The template to be used for the list. (string) + @return The list section. (string) + """ + lst = [] + for seeEntry in _list: + seeEntryString = ''.join(seeEntry) + lst.append(template.format( + **{'Link': html_uencode(self.__formatCrossReferenceEntry( + seeEntryString)), + })) + return '\n'.join(lst) + + def __processInlineTags(self, desc): + """ + Private method to process inline tags. + + @param desc One line of the description (string) + @return processed line with inline tags expanded (string) + @exception TagError raised to indicate an invalid tag + """ + start = desc.find('{@') + while start != -1: + stop = desc.find('}', start + 2) + if stop == -1: + raise TagError("Unterminated inline tag.\n{0}".format(desc)) + + tagText = desc[start + 1:stop] + if tagText.startswith('@link'): + parts = tagText.split(None, 1) + if len(parts) < 2: + raise TagError( + "Wrong format in inline tag {0}.\n{1}".format( + parts[0], desc)) + + formattedTag = self.__formatCrossReferenceEntry(parts[1]) + desc = desc.replace("{{{0}}}".format(tagText), formattedTag) + else: + tag = tagText.split(None, 1)[0] + raise TagError( + "Unknown inline tag encountered, {0}.\n{1}".format( + tag, desc)) + + start = desc.find('{@') + + return desc + + def __formatDescription(self, descr): + """ + Private method to format the contents of the documentation string. + + @param descr The contents of the documentation string. (string) + @exception TagError A tag doesn't have the correct number + of arguments. + @return The formatted contents of the documentation string. (string) + """ + if not descr: + return "" + + paragraphs = [] + paramList = [] + returns = [] + returnTypes = [] + yields = [] + yieldTypes = [] + exceptionDict = {} + signalDict = {} + eventDict = {} + deprecated = [] + authorInfo = [] + sinceInfo = [] + seeList = [] + lastItem = paragraphs + inTagSection = False + + dlist = descr.splitlines() + while dlist and not dlist[0]: + del dlist[0] + lastTag = "" + buffer = "" + for ditem in dlist: + ditem = self.__processInlineTags(ditem) + desc = ditem.strip() + if buffer: + if desc.startswith("@"): + buffer = "" + raise TagError( + "Wrong format in {0} line.\n".format(lastTag)) + else: + desc = buffer + desc + if desc: + if desc.startswith(("@param", "@keyparam")): + inTagSection = True + parts = desc.split(None, 2) + lastTag = parts[0] + if len(parts) < 2: + raise TagError( + "Wrong format in {0} line.\n".format(parts[0])) + paramName = parts[1] + if parts[0] == "@keyparam": + paramName += '=' + try: + paramList.append([paramName, "", [parts[2]]]) + except IndexError: + paramList.append([paramName, "", []]) + lastItem = paramList[-1][2] + elif desc.startswith("@type"): + parts = desc.split(None, 1) + if lastTag not in ["@param", "@keyparam"]: + raise TagError( + "{0} line must be preceded by a parameter line\n" + .format(parts[0])) + inTagSection = True + lastTag = parts[0] + if len(parts) < 2: + raise TagError( + "Wrong format in {0} line.\n".format(parts[0])) + paramList[-1][1] = parts[1] + elif desc.startswith("@ptype"): + inTagSection = True + parts = desc.split(None, 2) + lastTag = parts[0] + if len(parts) < 3: + raise TagError( + "Wrong format in {0} line.\n".format(parts[0])) + param, type_ = parts[1:] + for index in range(len(paramList)): + if paramList[index][0] == param: + paramList[index][1] = type_ + break + else: + raise TagError( + "Unknow parameter name '{0}' in {1} line.\n" + .format(param, parts[0])) + elif desc.startswith(("@return", "@ireturn")): + inTagSection = True + parts = desc.split(None, 1) + lastTag = parts[0] + if len(parts) < 2: + raise TagError( + "Wrong format in {0} line.\n".format(parts[0])) + returns = [parts[1]] + lastItem = returns + elif desc.startswith("@rtype"): + parts = desc.split(None, 1) + if lastTag not in ["@return", "@ireturn"]: + raise TagError( + "{0} line must be preceded by a @return line\n" + .format(parts[0])) + inTagSection = True + lastTag = parts[0] + if len(parts) < 2: + raise TagError( + "Wrong format in {0} line.\n".format(parts[0])) + returnTypes = [parts[1]] + lastItem = returnTypes + elif desc.startswith("@yield"): + inTagSection = True + parts = desc.split(None, 1) + lastTag = parts[0] + if len(parts) < 2: + raise TagError( + "Wrong format in {0} line.\n".format(parts[0])) + yields = [parts[1]] + lastItem = yields + elif desc.startswith("@ytype"): + parts = desc.split(None, 1) + if lastTag != "@yield": + raise TagError( + "{0} line must be preceded by a @yield line\n" + .format(parts[0])) + inTagSection = True + lastTag = parts[0] + if len(parts) < 2: + raise TagError( + "Wrong format in {0} line.\n".format(parts[0])) + yieldTypes = [parts[1]] + lastItem = yieldTypes + elif desc.startswith(("@exception", "@throws", "@raise")): + inTagSection = True + parts = desc.split(None, 2) + lastTag = parts[0] + if len(parts) < 2: + raise TagError( + "Wrong format in {0} line.\n".format(parts[0])) + excName = parts[1] + try: + exceptionDict[excName] = [parts[2]] + except IndexError: + exceptionDict[excName] = [] + lastItem = exceptionDict[excName] + elif desc.startswith("@signal"): + inTagSection = True + lastTag = desc.split(None, 1)[0] + m = _signal(desc, 0) + if m is None: + buffer = desc + else: + buffer = "" + signalName = ( + m.group("SignalName1") or m.group("SignalName2") + ) + signalDesc = ( + m.group("SignalDescription1") or + m.group("SignalDescription2") + ) + signalDict[signalName] = [] + if signalDesc is not None: + signalDict[signalName].append(signalDesc) + lastItem = signalDict[signalName] + elif desc.startswith("@event"): + inTagSection = True + lastTag = desc.split(None, 1)[0] + m = _event(desc, 0) + if m is None: + buffer = desc + else: + buffer = "" + eventName = ( + m.group("EventName1") or m.group("EventName2") + ) + eventDesc = ( + m.group("EventDescription1") or + m.group("EventDescription2") + ) + eventDict[eventName] = [] + if eventDesc is not None: + eventDict[eventName].append(eventDesc) + lastItem = eventDict[eventName] + elif desc.startswith("@deprecated"): + inTagSection = True + parts = desc.split(None, 1) + lastTag = parts[0] + if len(parts) < 2: + raise TagError( + "Wrong format in {0} line.\n".format(parts[0])) + deprecated = [parts[1]] + lastItem = deprecated + elif desc.startswith("@author"): + inTagSection = True + parts = desc.split(None, 1) + lastTag = parts[0] + if len(parts) < 2: + raise TagError( + "Wrong format in {0} line.\n".format(parts[0])) + authorInfo = [parts[1]] + lastItem = authorInfo + elif desc.startswith("@since"): + inTagSection = True + parts = desc.split(None, 1) + lastTag = parts[0] + if len(parts) < 2: + raise TagError( + "Wrong format in {0} line.\n".format(parts[0])) + sinceInfo = [parts[1]] + lastItem = sinceInfo + elif desc.startswith("@see"): + inTagSection = True + parts = desc.split(None, 1) + lastTag = parts[0] + if len(parts) < 2: + raise TagError( + "Wrong format in {0} line.\n".format(parts[0])) + seeList.append([parts[1]]) + lastItem = seeList[-1] + elif desc.startswith("@@"): + lastItem.append(desc[1:]) + elif desc.startswith("@"): + tag = desc.split(None, 1)[0] + raise TagError( + "Unknown tag encountered, {0}.\n".format(tag)) + else: + lastItem.append(ditem) + elif not inTagSection: + lastItem.append(ditem) + + description = self.__genParagraphs(paragraphs) if paragraphs else "" + + parameterSect = ( + TemplatesListsStyleCSS.parametersListTemplate.format( + **{'Parameters': self.__genParamDescriptionListSection( + paramList)}) + if paramList else + "" + ) + + returnSect = ( + TemplatesListsStyleCSS.returnsTemplate.format( + html_uencode('\n'.join(returns))) + if returns else + "" + ) + + returnTypesSect = ( + TemplatesListsStyleCSS.returnTypesTemplate.format( + html_uencode('\n'.join(returnTypes))) + if returnTypes else + "" + ) + + yieldSect = ( + TemplatesListsStyleCSS.yieldsTemplate.format( + html_uencode('\n'.join(yields))) + if yields else + "" + ) + + yieldTypesSect = ( + TemplatesListsStyleCSS.yieldTypesTemplate.format( + html_uencode('\n'.join(yieldTypes))) + if yieldTypes else + "" + ) + + exceptionSect = ( + TemplatesListsStyleCSS.exceptionsListTemplate.format( + **{'Exceptions': self.__genDescriptionListSection( + exceptionDict, + TemplatesListsStyleCSS.exceptionsListEntryTemplate)} + ) + if exceptionDict else + "" + ) + + signalSect = ( + TemplatesListsStyleCSS.signalsListTemplate.format( + **{'Signals': self.__genDescriptionListSection( + signalDict, + TemplatesListsStyleCSS.signalsListEntryTemplate)} + ) + if signalDict else + "" + ) + + eventSect = ( + TemplatesListsStyleCSS.eventsListTemplate.format( + **{'Events': self.__genDescriptionListSection( + eventDict, + TemplatesListsStyleCSS.eventsListEntryTemplate)} + ) + if eventDict else + "" + ) + + deprecatedSect = ( + TemplatesListsStyleCSS.deprecatedTemplate.format( + **{'Lines': html_uencode('\n'.join(deprecated))}) + if deprecated else + "" + ) + + authorInfoSect = ( + TemplatesListsStyleCSS.authorInfoTemplate.format( + **{'Authors': html_uencode('\n'.join(authorInfo))}) + if authorInfo else + "" + ) + + sinceInfoSect = ( + TemplatesListsStyleCSS.sinceInfoTemplate.format( + **{'Info': html_uencode(sinceInfo[0])}) + if sinceInfo else + "" + ) + + seeSect = ( + TemplatesListsStyleCSS.seeListTemplate.format( + **{'Links': self.__genSeeListSection( + seeList, TemplatesListsStyleCSS.seeListEntryTemplate)}) + if seeList else + '' + ) + + return "".join([ + deprecatedSect, description, parameterSect, returnSect, + returnTypesSect, yieldSect, yieldTypesSect, exceptionSect, + signalSect, eventSect, authorInfoSect, seeSect, sinceInfoSect, + ]) + + def getQtHelpKeywords(self): + """ + Public method to retrieve the parts for the QtHelp keywords section. + + @return list of tuples containing the name (string) and the ref + (string). The ref is without the filename part. + """ + if not self.generated: + self.genDocument() + + return self.keywords