eric7/DocumentationTools/ModuleDocumentor.py

Sun, 12 Sep 2021 20:22:27 +0200

author
Detlev Offenbach <detlev@die-offenbachs.de>
date
Sun, 12 Sep 2021 20:22:27 +0200
branch
eric7
changeset 8596
d64760b2da50
parent 8312
800c432b34c8
child 8598
0460087da31b
permissions
-rw-r--r--

Documentation Generator: streamlined the code to use an external style sheet file.

# -*- coding: utf-8 -*-

# Copyright (c) 2003 - 2021 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 outputDir The output directory for the files
        @type str
        """
        self.module = module
        self.empty = True
        
        # TODO: replace these assignments
        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.yieldsTemplate = TemplatesListsStyleCSS.yieldsTemplate
        self.yieldTypesTemplate = (
            TemplatesListsStyleCSS.yieldTypesTemplate)
        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
        
        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}
            ) +
            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()
        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(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 "",
                   }))
            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(
                [self.listEntrySimpleTemplate.format(**{'Name': name})
                 for name in attrNames])
            if attrNames else
            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
            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 = 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:
            with contextlib.suppress(KeyError):
                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__')))
        
        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
            supers = ', '.join(supers) if len(supers) > 0 else "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 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 = (
            self.parametersListTemplate.format(
                **{'Parameters': self.__genParamDescriptionListSection(
                    paramList)})
            if paramList else
            ""
        )
        
        returnSect = (
            self.returnsTemplate.format(
                html_uencode('\n'.join(returns)))
            if returns else
            ""
        )
        
        returnTypesSect = (
            self.returnTypesTemplate.format(
                html_uencode('\n'.join(returnTypes)))
            if returnTypes else
            ""
        )
        
        yieldSect = (
            self.yieldsTemplate.format(
                html_uencode('\n'.join(yields)))
            if yields else
            ""
        )
        
        yieldTypesSect = (
            self.yieldTypesTemplate.format(
                html_uencode('\n'.join(yieldTypes)))
            if yieldTypes else
            ""
        )
        
        exceptionSect = (
            self.exceptionsListTemplate.format(
                **{'Exceptions': self.__genDescriptionListSection(
                    exceptionDict, self.exceptionsListEntryTemplate)})
            if exceptionDict else
            ""
        )
        
        signalSect = (
            self.signalsListTemplate.format(
                **{'Signals': self.__genDescriptionListSection(
                    signalDict, self.signalsListEntryTemplate)})
            if signalDict else
            ""
        )
        
        eventSect = (
            self.eventsListTemplate.format(
                **{'Events': self.__genDescriptionListSection(
                    eventDict, self.eventsListEntryTemplate)})
            if eventDict else
            ""
        )
        
        deprecatedSect = (
            self.deprecatedTemplate.format(
                **{'Lines': html_uencode('\n'.join(deprecated))})
            if deprecated else
            ""
        )
        
        authorInfoSect = (
            self.authorInfoTemplate.format(
                **{'Authors': html_uencode('\n'.join(authorInfo))})
            if authorInfo else
            ""
        )
        
        sinceInfoSect = (
            self.sinceInfoTemplate.format(
                **{'Info': html_uencode(sinceInfo[0])})
            if sinceInfo else
            ""
        )
        
        seeSect = (
            self.seeListTemplate.format(
                **{'Links': self.__genSeeListSection(
                    seeList, self.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

eric ide

mercurial