--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/eric6/DocumentationTools/ModuleDocumentor.py Sun Apr 14 15:09:21 2019 +0200 @@ -0,0 +1,1205 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2003 - 2019 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. +""" + +from __future__ import unicode_literals + +import sys +import re + +from Utilities import html_uencode +from Utilities.ModuleParser import RB_SOURCE, Function + +_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(object): + """ + Class implementing the builtin documentation generator. + """ + def __init__(self, module, colors, stylesheet=None): + """ + Constructor + + @param module the information of the parsed Python file + @param colors dictionary specifying the various colors for the output + (dictionary of strings) + @param stylesheet the style to be used for the generated pages (string) + """ + self.module = module + self.empty = True + + self.stylesheet = stylesheet + + if self.stylesheet: + from . import TemplatesListsStyleCSS + self.headerTemplate = TemplatesListsStyleCSS.headerTemplate + self.footerTemplate = TemplatesListsStyleCSS.footerTemplate + self.moduleTemplate = TemplatesListsStyleCSS.moduleTemplate + self.rbFileTemplate = TemplatesListsStyleCSS.rbFileTemplate + self.classTemplate = TemplatesListsStyleCSS.classTemplate + self.methodTemplate = TemplatesListsStyleCSS.methodTemplate + self.constructorTemplate = \ + TemplatesListsStyleCSS.constructorTemplate + self.rbModuleTemplate = TemplatesListsStyleCSS.rbModuleTemplate + self.rbModulesClassTemplate = \ + TemplatesListsStyleCSS.rbModulesClassTemplate + self.functionTemplate = TemplatesListsStyleCSS.functionTemplate + self.listTemplate = TemplatesListsStyleCSS.listTemplate + self.listEntryTemplate = TemplatesListsStyleCSS.listEntryTemplate + self.listEntryNoneTemplate = \ + TemplatesListsStyleCSS.listEntryNoneTemplate + self.listEntryDeprecatedTemplate = \ + TemplatesListsStyleCSS.listEntryDeprecatedTemplate + self.listEntrySimpleTemplate = \ + TemplatesListsStyleCSS.listEntrySimpleTemplate + self.paragraphTemplate = TemplatesListsStyleCSS.paragraphTemplate + self.parametersListTemplate = \ + TemplatesListsStyleCSS.parametersListTemplate + self.parameterTypesListEntryTemplate = \ + TemplatesListsStyleCSS.parameterTypesListEntryTemplate + self.parametersListEntryTemplate = \ + TemplatesListsStyleCSS.parametersListEntryTemplate + self.returnsTemplate = TemplatesListsStyleCSS.returnsTemplate + self.returnTypesTemplate = \ + TemplatesListsStyleCSS.returnTypesTemplate + self.exceptionsListTemplate = \ + TemplatesListsStyleCSS.exceptionsListTemplate + self.exceptionsListEntryTemplate = \ + TemplatesListsStyleCSS.exceptionsListEntryTemplate + self.signalsListTemplate = \ + TemplatesListsStyleCSS.signalsListTemplate + self.signalsListEntryTemplate = \ + TemplatesListsStyleCSS.signalsListEntryTemplate + self.eventsListTemplate = TemplatesListsStyleCSS.eventsListTemplate + self.eventsListEntryTemplate = \ + TemplatesListsStyleCSS.eventsListEntryTemplate + self.deprecatedTemplate = TemplatesListsStyleCSS.deprecatedTemplate + self.authorInfoTemplate = TemplatesListsStyleCSS.authorInfoTemplate + self.seeListTemplate = TemplatesListsStyleCSS.seeListTemplate + self.seeListEntryTemplate = \ + TemplatesListsStyleCSS.seeListEntryTemplate + self.seeLinkTemplate = TemplatesListsStyleCSS.seeLinkTemplate + self.sinceInfoTemplate = TemplatesListsStyleCSS.sinceInfoTemplate + else: + from . import TemplatesListsStyle + self.headerTemplate = \ + TemplatesListsStyle.headerTemplate.format(**colors) + self.footerTemplate = \ + TemplatesListsStyle.footerTemplate.format(**colors) + self.moduleTemplate = \ + TemplatesListsStyle.moduleTemplate.format(**colors) + self.rbFileTemplate = \ + TemplatesListsStyle.rbFileTemplate.format(**colors) + self.classTemplate = \ + TemplatesListsStyle.classTemplate.format(**colors) + self.methodTemplate = \ + TemplatesListsStyle.methodTemplate.format(**colors) + self.constructorTemplate = \ + TemplatesListsStyle.constructorTemplate.format(**colors) + self.rbModuleTemplate = \ + TemplatesListsStyle.rbModuleTemplate.format(**colors) + self.rbModulesClassTemplate = \ + TemplatesListsStyle.rbModulesClassTemplate.format(**colors) + self.functionTemplate = \ + TemplatesListsStyle.functionTemplate.format(**colors) + self.listTemplate = \ + TemplatesListsStyle.listTemplate.format(**colors) + self.listEntryTemplate = \ + TemplatesListsStyle.listEntryTemplate.format(**colors) + self.listEntryNoneTemplate = \ + TemplatesListsStyle.listEntryNoneTemplate.format(**colors) + self.listEntryDeprecatedTemplate = \ + TemplatesListsStyle.listEntryDeprecatedTemplate.format( + **colors) + self.listEntrySimpleTemplate = \ + TemplatesListsStyle.listEntrySimpleTemplate.format(**colors) + self.paragraphTemplate = \ + TemplatesListsStyle.paragraphTemplate.format(**colors) + self.parametersListTemplate = \ + TemplatesListsStyle.parametersListTemplate.format(**colors) + self.parametersListEntryTemplate = \ + TemplatesListsStyle.parametersListEntryTemplate.format( + **colors) + self.parameterTypesListEntryTemplate = \ + TemplatesListsStyle.parameterTypesListEntryTemplate.format( + **colors) + self.returnsTemplate = \ + TemplatesListsStyle.returnsTemplate.format(**colors) + self.returnTypesTemplate = \ + TemplatesListsStyle.returnTypesTemplate.format(**colors) + self.exceptionsListTemplate = \ + TemplatesListsStyle.exceptionsListTemplate.format(**colors) + self.exceptionsListEntryTemplate = \ + TemplatesListsStyle.exceptionsListEntryTemplate.format( + **colors) + self.signalsListTemplate = \ + TemplatesListsStyle.signalsListTemplate.format(**colors) + self.signalsListEntryTemplate = \ + TemplatesListsStyle.signalsListEntryTemplate.format(**colors) + self.eventsListTemplate = \ + TemplatesListsStyle.eventsListTemplate.format(**colors) + self.eventsListEntryTemplate = \ + TemplatesListsStyle.eventsListEntryTemplate.format(**colors) + self.deprecatedTemplate = \ + TemplatesListsStyle.deprecatedTemplate.format(**colors) + self.authorInfoTemplate = \ + TemplatesListsStyle.authorInfoTemplate.format(**colors) + self.seeListTemplate = \ + TemplatesListsStyle.seeListTemplate.format(**colors) + self.seeListEntryTemplate = \ + TemplatesListsStyle.seeListEntryTemplate.format(**colors) + self.seeLinkTemplate = \ + TemplatesListsStyle.seeLinkTemplate.format(**colors) + self.sinceInfoTemplate = \ + TemplatesListsStyle.sinceInfoTemplate.format(**colors) + + 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 = self.headerTemplate.format( + **{'Title': self.module.name, + 'Style': self.stylesheet}) + \ + self.__genModuleSection() + \ + self.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 = self.rbFileTemplate.format( + **{'Module': self.module.name, + 'ModuleDescription': + self.__formatDescription(self.module.description), + 'GlobalsList': globalsList, + 'ClassList': classList, + 'RbModulesList': rbModulesList, + 'FunctionList': functionList, + }) + else: + modBody = self.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() + if self.module.type == RB_SOURCE: + rbModulesSection = self.__genRbModulesSection() + else: + rbModulesSection = "" + 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(self.listEntryTemplate.format( + **{'Link': "{0}".format(name), + 'Name': sectionDict[name].name, + 'Description': + self.__getShortDescription(sectionDict[name].description), + 'Deprecated': + self.__checkDeprecated(sectionDict[name].description) and + self.listEntryDeprecatedTemplate or "", + })) + if kwSuffix: + n = "{0} ({1})".format(name, kwSuffix) + else: + n = "{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 = [] + if class_ is not None: + scope = class_ + else: + scope = self.module + attrNames = sorted(attr for attr in scope.globals.keys() + if not scope.globals[attr].isSignal) + if attrNames: + s = ''.join( + [self.listEntrySimpleTemplate.format(**{'Name': name}) + for name in attrNames]) + else: + s = self.listEntryNoneTemplate + return self.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(list(self.module.classes.keys())) + if names: + self.empty = False + s = self.__genListSection(names, self.module.classes) + else: + s = self.listEntryNoneTemplate + return self.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(list(self.module.modules.keys())) + if names: + self.empty = False + s = self.__genListSection(names, self.module.modules) + else: + s = self.listEntryNoneTemplate + return self.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(list(self.module.functions.keys())) + if names: + self.empty = False + s = self.__genListSection(names, self.module.functions) + else: + s = self.listEntryNoneTemplate + return self.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(list(self.module.classes.keys())) + classes = [] + for className in classNames: + _class = self.module.classes[className] + supers = _class.super + if len(supers) > 0: + supers = ', '.join(supers) + else: + supers = '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 = self.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: + try: + lst.append(self.listEntryTemplate.format( + **{'Link': "{0}.{1}".format(className, '__init__'), + 'Name': clsName, + 'Description': self.__getShortDescription( + sectionDict['__init__'].description), + 'Deprecated': self.__checkDeprecated( + sectionDict['__init__'].description) and + self.listEntryDeprecatedTemplate or "", + })) + self.keywords.append( + ("{0} (Constructor)".format(className), + "#{0}.{1}".format(className, '__init__'))) + except KeyError: + pass + + for name in names: + lst.append(self.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 + self.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 = self.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 = self.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 = self.listEntryNoneTemplate + return (self.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(list(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 = self.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(list(obj.classes.keys())) + classes = [] + for className in classNames: + _class = obj.classes[className] + supers = _class.super + if len(supers) > 0: + supers = ', '.join(supers) + else: + supers = 'None' + + methList, methBodies = \ + self.__genMethodSection(_class, className, Function.General) + + try: + clsBody = self.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 = self.listEntryNoneTemplate + return (self.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(self.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 + self.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(list(self.module.functions.keys())) + for funcName in funcNames: + try: + funcBody = self.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(self.paragraphTemplate.format( + **{'Lines': '\n'.join(linelist)})) + linelist = [] + if linelist: + lst.append(self.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(list(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(self.parameterTypesListEntryTemplate.format( + **{'Name': name, + 'Type': type_, + 'Description': html_uencode('\n'.join(lines)), + })) + else: + lst.append(self.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 self.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 formated contents of the documentation string. (string) + """ + if not descr: + return "" + + paragraphs = [] + paramList = [] + returns = [] + returnTypes = [] + exceptionDict = {} + signalDict = {} + eventDict = {} + deprecated = [] + authorInfo = [] + sinceInfo = [] + seeList = [] + lastItem = paragraphs + inTagSection = False + + dlist = descr.splitlines() + while dlist and not dlist[0]: + del dlist[0] + lastTag = "" + for ditem in dlist: + ditem = self.__processInlineTags(ditem) + desc = ditem.strip() + 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(("@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: + raise TagError("Wrong format in @signal line.\n") + signalName = 1 and m.group("SignalName1") \ + or m.group("SignalName2") + signalDesc = 1 and 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: + raise TagError( + "Wrong format in {0} line.\n".format(parts[0])) + eventName = 1 and m.group("EventName1") \ + or m.group("EventName2") + eventDesc = 1 and 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) + + if paragraphs: + description = self.__genParagraphs(paragraphs) + else: + description = "" + + if paramList: + parameterSect = self.parametersListTemplate.format( + **{'Parameters': self.__genParamDescriptionListSection( + paramList)}) + else: + parameterSect = "" + + if returns: + returnSect = self.returnsTemplate.format( + html_uencode('\n'.join(returns))) + else: + returnSect = "" + + if returnTypes: + returnTypesSect = self.returnTypesTemplate.format( + html_uencode('\n'.join(returnTypes))) + else: + returnTypesSect = "" + + if exceptionDict: + exceptionSect = self.exceptionsListTemplate.format( + **{'Exceptions': self.__genDescriptionListSection( + exceptionDict, self.exceptionsListEntryTemplate)}) + else: + exceptionSect = "" + + if signalDict: + signalSect = self.signalsListTemplate.format( + **{'Signals': self.__genDescriptionListSection( + signalDict, self.signalsListEntryTemplate)}) + else: + signalSect = "" + + if eventDict: + eventSect = self.eventsListTemplate.format( + **{'Events': self.__genDescriptionListSection( + eventDict, self.eventsListEntryTemplate)}) + else: + eventSect = "" + + if deprecated: + deprecatedSect = self.deprecatedTemplate.format( + **{'Lines': html_uencode('\n'.join(deprecated))}) + else: + deprecatedSect = "" + + if authorInfo: + authorInfoSect = self.authorInfoTemplate.format( + **{'Authors': html_uencode('\n'.join(authorInfo))}) + else: + authorInfoSect = "" + + if sinceInfo: + sinceInfoSect = self.sinceInfoTemplate.format( + **{'Info': html_uencode(sinceInfo[0])}) + else: + sinceInfoSect = "" + + if seeList: + seeSect = self.seeListTemplate.format( + **{'Links': self.__genSeeListSection( + seeList, self.seeListEntryTemplate)}) + else: + seeSect = '' + + return "{0}{1}{2}{3}{4}{5}{6}{7}{8}{9}{10}".format( + deprecatedSect, description, parameterSect, returnSect, + returnTypesSect, 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