eric6/DocumentationTools/ModuleDocumentor.py

changeset 6942
2602857055c5
parent 6645
ad476851d7e0
child 7229
53054eb5b15a
--- /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

eric ide

mercurial