"""cppDocLib.py: Extract special comments from C++-Source and generate HTML
"""

import os
import re
import regex
import regsub
import rfc822
import string
import sys
import time
import traceback
import urllib

import fileutil
from html import *
from acquisition import Acquest
import c2html

version = "1.2.01"
fullBacktrace = 1
strict = None    # global should be replaced by acquisition

_backlinkHTML = ('<A HREF="#%s">'
    + '<IMG SRC="http://pwww.bea.sap-ag.de/Images/QBullets/scrollup.gif" '
    + 'ALT="Top" BORDER="0"></A>')

def stdwarn (msg):
    sys.stderr.write ('Warning: ' + msg + '\n')

_warn = stdwarn

class CppDoc (Acquest):
    tabSize = 8
    doHighlight = None
    strict = None
    quoteHTML = None

    def __init__ (self, bodyAttributes = None):
        self.bodyAttributes = bodyAttributes
        self.currentGroup = DocGroup ()
        global _warn
        _warn = self.warning

    commentRE = regex.compile ('/[*]!')

    def initNewFile (self, fname):
        self.fname = fname
        self.tabFound = None
        self.specialIdentifier = []

    def warning (self, msg):
        sys.stderr.write ('Warning: %s: %s\n' % (self.fname, msg))

    def tabWarning (self):
        if self.tabFound:
            return
        self.warning ('found tabs')
        self.tabFound = 1

    def readSource (self, fname, makeClean = None):
        global strict
        strict = self.acquire ('strict')
        self.initNewFile (fname)
        self.lockAcquisitions ('weaver')
        self.weaver.setCurrentFile (fileutil.basename (fname) + '.html')
        re = self.commentRE
        source = open (fname, "r").read ()
        if makeClean:
            cleanSource = []
        pos = re.search (source)
        if pos == -1:
            raise 'Module ' + fname + 'has no special comments'
        if makeClean:
            cleanSource.append (source [:pos])
        ok = 1
        try:
            comment, nextPos = self.readComment (source, pos)
            commentEnd = nextPos
            category, self.title, items = self.parseBlock (comment)
            if string.lower (category) != 'module':
                sys.stderr.write ("Error: The first special comment should be a 'Module', not a '%s'\n"
                        % category)
                sys.exit (2)
            self.masthead = MastHead ('Module', items)
            self.currentGroup.addItem (self.masthead)
            pos = re.search (source, nextPos)
            while pos != -1:
                if makeClean:
                    cleanSource.append (source [commentEnd:pos])
                comment, nextPos = self.readComment (source, pos)
                category, title, items = self.parseBlock (comment)
                item = self.createItem (category, title, items)
                commentEnd = nextPos
                if item != None:
                    nextPos = item.readSource (source, nextPos, self)
                    item.addToIndex (self.weaver)
                try:
                    pos = re.search (source, nextPos)
                except regex.error:
                    # quick fix, why is pos out of range?
                    pos = -1
            if makeClean:
                cleanSource.append (source [commentEnd:pos])
                self.makeClean (cleanSource, source, fname)
            if not self.currentGroup.isTop ():
                sys.stderr.write ('%s: group not closed %s\n'
                    % (fname, self.currentGroup.dumpString ()))
                ok = None
        except CppDocError, perr:
            perr.dump (fname, self.findErrorLine (source, pos))
            ok = None
        except:
            traceback.print_exc ()
            sys.stderr.write ('%s (line %d): Parse error\n' %
                (fname, self.findErrorLine (source, pos)))
            ok = None
        return ok

    def readComment (self, source, startPos):
        startPos = startPos + 3
        endPos = string.index (source, '*/', startPos)
        nextPos = endPos + 2
        if (source [nextPos] == '\n'):
            nextPos = nextPos + 1
        return source [startPos:endPos], nextPos

    def createItem (self, category, title, items):
        category = string.lower (category)
        item = None
        if category == 'function':
            item = FunctionDoc (title, items, self.currentGroup.title)
            self.currentGroup.addItem (item)
        elif category == 'declaration':
            item = DeclarationDoc (title, items)
            self.currentGroup.addItem (item)
        elif category in ['notes', 'example']:
            item = self.currentGroup.addItem (DocLeaf (title, items))
        elif category == 'chapter':
            item = self.currentGroup = self.currentGroup.pushGroup (title, items)
        elif category == 'endchapter':
            self.currentGroup = self.currentGroup.popGroup ()
        elif category == 'class':
            item = self.currentGroup = self.currentGroup.pushGroup (
                title, items, ClassGroup)
            self.specialIdentifier.append (title)
        elif category == 'endclass':
            if strict:
                checkForGroupMismatch (category,
                    self.currentGroup.title, title)
            self.currentGroup = self.currentGroup.popGroup ()
        elif category == 'endfunction':
            pass # simply ignore
        else:
            self.currentGroup.addItem (DocLeaf (title, items))
        return item

    def genHTML (self, fname):
        title = self.getTitle ()
        if self.doHighlight and self.specialIdentifier:
            htmlstream = HighlightStream (fname, title, self.bodyAttributes, self.specialIdentifier)
        else:
            htmlstream = HTMLFile (fname, title, self.bodyAttributes)
        htmlstream.titleHeader (title)
        htmlstream.pushTag ('blockquote')
        self.currentGroup.genTOC (htmlstream)
        htmlstream.line ()
        self.currentGroup.genHTML (htmlstream)
        self.genDate (htmlstream)
        htmlstream.popTag ()
        htmlstream.close ()

    def getTitle (self):
        specialEntry = self.masthead.subitems.findCategory ('special area')
        if specialEntry == None:
            result = self.title
        else:
            result = self.title + ': ' + specialEntry.text ()
        return result

    def genDate (self, htmlstream):
        now = time.localtime(time.time ())
        htmlstream.write (time.strftime ("<i>Generated %Y-%m-%d</i>\n", now))

    def findOldClean (self, source, startRE, endRE):
        startPos = startRE.search (source)
        if startPos == -1:
            return -1, -1
        endPos = endRE.search (source, startPos)
        endPos = endRE.regs [0][1]
        return startPos, endPos

    def makeClean (self, snippets, source, fname):
        cleanSource = string.join (snippets, '')
        ifdef = '#if defined (CPPDOC_CLEANSOURCE)'
        endif = '#endif  /* CPPDOC_CLEANSOURCE */'
        endifre = '#endif  /[*] CPPDOC_CLEANSOURCE [*]/'
        ifdefRE = regex.compile (ifdef)
        endifRE = regex.compile (endifre)
        startPos, endPos = self.findOldClean (cleanSource, ifdefRE, endifRE)
        if startPos != -1:
            cleanSource = cleanSource [:startPos] + cleanSource [endPos:]
        # create new source
        startPos, endPos = self.findOldClean (source, ifdefRE, endifRE)
        if startPos != -1:
            source = "%s%s\n%s\n%s%s" % (source [:startPos],
                    ifdef, cleanSource, endif, source [endPos:])
        else:
            source = "%s\n%s\n%s\n%s\n" % (source, ifdef, cleanSource, endif)
        # make backup
        fileutil.makeBackup (fname)
        # write source to file
        open (fname, "w").write (source)

    def parseBlock (self, text):
        textLen = len (text)
        text = string.expandtabs (text, self.tabSize)
        if len (text) > textLen:
            self.tabWarning ()
        lines = string.split (text, '\n')
        lines = removeDecoLines (lines)
        leadingSkip = countSkip (lines, [_blankRE, _commentRE])
        lines = stripLead (lines, leadingSkip)
        rawItems = breakSubitems (lines)
        if len (rawItems) == 0:
            raise InvalidBlockError (lines)
        docCategory, categoryLines = rawItems [0]
        title = string.strip (categoryLines [0])
        subitems = SubItems ()
        for category, itemLines in rawItems [1:]:
            if self.quoteHTML and not (category in _htmlAllowed):
                itemLines = entityfyList (itemLines)
            handler = findCategoryHandler (category)
            try:
                entry = handler (itemLines)
                subitems.addItem (category, entry)
            except:
                raise ParseError (category + ':' + string.join (itemLines, '\n'))
        return docCategory, title, subitems

    def findErrorLine (self, source, commentStart):
        pos = 0
        line = 0
        index = string.index
        while pos < commentStart:
            pos = index (source, '\n', pos) + 1
            line = line + 1
        return line

class DocItem:
    closeWithLine = None
    def __init__ (self, title, subitems = None):
        self.title = title
        self.anchor = urllib.quote (title, '')
        self.subitems = subitems

    def acquire (self, name, default = None):
        try:
            result = getattr (self, name)
        except AttributeError:
            try:
                result = self.acquisitionContext.acquire (name, default)
            except AttributeError:
                result = default
        return result

    def genTOC (self, htmlstream):
        pass

    def shortDesc (self):
        if self.subitems == None:
            return ''
        description = self.subitems.findCategory ('description')
        if description != None:
            result = description.shortDesc ()
        else:
            result = ''
        return result

    def tocLink (self):
        property = self.shortProperty ('new', 'created', 'updated', 'obsolete')
        link = href ('#' + self.anchor, self.title)
        shortDesc = property + self.shortDesc ()
        if shortDesc:
            shortDesc = ' - ' + shortDesc
        else:
            shortDesc = ''
        return link + shortDesc + '\n'

    def shortProperty (self, *names):
        for name in names:
            property = self.subitems.findCategory (name)
            if property:
                text = property.shortForm ()
                if text:
                    return text + ' '
        return ''

    def genHeader (self, htmlstream):
        if not self.title:
            raise 'empty element'
        backlink = self.acquire ('tocTarget')
        if backlink:
            backlink = ' ' + (_backlinkHTML % backlink)
        else:
            backlink = ''
        fmt = '<H3><A NAME="%s">%s</A>%s</H3>\n'
        result = fmt % (self.anchor, self.title, backlink)
        htmlstream.write (result)
        return result

    def genSubitems (self, htmlstream, exceptlist = []):
        relevant = []
        for category, value in self.subitems.items ():
            if not string.lower (category) in exceptlist:
                relevant.append ((category, value))
        if len (relevant) == 0:
            return
        htmlstream.pushTag ('dl')
        for category, value in relevant:
            htmlstream.tag ('dt')
            htmlstream.write (tagged ('b', category) + ':\n')
            htmlstream.tag ('dd')
            value.genHTML (htmlstream)
            htmlstream.lineBreak ()
        htmlstream.popTag ()

    def genSpecificHTML (self, htmlstream):
        self.genSubitems (htmlstream)

    def genHTML (self, htmlstream):
        if not self.title:
            return
        self.genHeader (htmlstream)
        htmlstream.pushTag ('blockquote')
        self.genSpecificHTML (htmlstream)
        htmlstream.popTag ()
        if self.closeWithLine:
            htmlstream.line ()

    def readSource (self, source, pos, controller):
        """Read any additional info from the source.

        An example would be prototypes. The method returns the new
        position inside the source.
        """
        return pos

    def addToIndex (self, weaver):
        weaver.addAnything (self.title)

class DocGroup (DocItem):
    def __init__ (self, title = '', superGroup = None, subitems = None):
        DocItem.__init__ (self, title, subitems)
        self.items = []
        self.superGroup = superGroup
        if superGroup == None:
            self.nesting = 0
        else:
            self.nesting = superGroup.nesting + 1

    def addItem (self, item):
        self.items.append (item)
        item.acquisitionContext = self

    def pushGroup (self, title, subitems = None, constructor = None):
        if constructor == None:
            constructor = DocGroup
        newGroup = constructor (title, self, subitems)
        self.addItem (newGroup)
        return newGroup

    def popGroup (self):
        if (self.superGroup == None):
            return self
        else:
            return self.superGroup

    def tocLink (self):
        link = DocItem.tocLink (self)
        if self.title:
            self.tocTarget = urllib.quote (self.title + '_TOC', '/')
            result = anchor (self.tocTarget, '') + link
        else:
            result = link
        return result

    def genTOC (self, htmlstream):
        htmlstream.pushTag ('ul')
        htmlstream.pseudoBreak ()
        for item in self.items:
            htmlstream.tag ('li')
            htmlstream.write (item.tocLink ())
            item.genTOC (htmlstream)
        htmlstream.popTag ()
        htmlstream.pseudoBreak ()

    def genHTML (self, htmlstream):
        DocItem.genHTML (self, htmlstream)
        for item in self.items:
            item.genHTML (htmlstream)

    def __str__ (self):
        return "<%s '%s' %x>" % (
            self.__class__.__name__, self.title, id (self))

    def dumpString (self):
        selfString = str (self)
        if self.superGroup != None:
            superString = self.superGroup.dumpString ()
            selfString = selfString + ' in ' + superString
        return selfString

    def isTop (self):
        return self.superGroup == None

class ClassGroup (DocGroup):
    def __init__ (self, className, superGroup, subitems):
        DocGroup.__init__ (self, 'Class ' + className, superGroup, subitems)
        self.className = className
        self.superClasses = []

    def readSource (self, source, pos, controller):
        bracePos = string.find (source, '{', pos)
        if bracePos == -1:
            result = pos
        else:
            self.parseClassInfo (source [pos:bracePos])
            result = bracePos
        return result

    templateRE = re.compile ('template *<')
    classRE = re.compile ('(.*)class +([a-zA-Z0-9_]+)', re.DOTALL)

    def parseClassInfo (self, data):
        data = string.strip (data)
        match = self.templateRE.match (data)
        if match:
            pos = findMatching ('<>', data, match.end (0))
            data = data [pos:]
        match = self.classRE.match (data)
        if match != None:
            className, templateInfo = match.group (2, 1)
            assert self.className == className
            superInfo = string.strip (data [match.end (0):])
            if superInfo:
                self.parseSuperClass (superInfo)

    superClassRE = re.compile (' *(private|protected|public)? *([a-zA-Z0-9_]+) *(<?)')

    def parseSuperClass (self, superInfo):
        superInfo = string.replace (superInfo, '\n', ' ')
        superInfo = string.replace (superInfo, 'virtual', '')
        pos = string.index (superInfo, ':') + 1
        match = self.superClassRE.match (superInfo, pos)
        while match != None:
            visibility, name, templateStart = match.group (1, 2, 3)
            if not visibility:
                visibility = 'private'
            pos = match.end (0)
            if templateStart == '<':
                pos = findMatching ('<>', superInfo, pos)
            self.superClasses.append ((name, visibility, templateStart))
            stopPos = string.find (superInfo, ',', pos)
            if stopPos == -1:
                match = None
            else:
                match = self.superClassRE.match (superInfo, pos + 1)

    def addToIndex (self, weaver):
        weaver.addClass (self.className)
        if self.superClasses:
            superclassesEntry = SuperclassesEntry (self.superClasses, weaver)
            self.subitems.insertItem (0, 'superclasses', superclassesEntry)

def findMatching (pair, data, pos):
    intro = pair [0]
    exit = pair [1]
    nesting = 1
    while nesting > 0:
        current = data [pos]
        if current == intro:
            nesting = nesting + 1
        elif current == exit:
            nesting = nesting - 1
        pos = pos + 1
    return pos

class MastHead (DocItem):
    closeWithLine = 1

    def __init__ (self, title, subitems = None):
        DocItem.__init__ (self, title, subitems)
        self.addReleaseEntry ()

    def getRelease (self):
        try:
            releaseFile = os.path.join (os.environ ['OWN'], 'Release.dat')
            result = open (releaseFile, 'r').read ()
            result = string.strip (result)
        except KeyError:
            result = None
        return result

    def addReleaseEntry (self):
        release = self.getRelease ()
        if release != None:
            self.subitems.addItem ('release', ReleaseEntry (release))

class DocLeaf (DocItem):
    closeWithLine = 1

    def genSpecificHTML (self, htmlstream):
        description = self.subitems.findCategory ('description')
        if description != None:
            htmldescription = self.subitems.findCategory ('htmldescription')
            if htmldescription != None:
                description.paragraphs = (description.paragraphs
                    + htmldescription.paragraphs)
            description.genHTML (htmlstream)
        self.genSubitems (htmlstream, ['description', 'htmldescription', 'prototypes'])

class FunctionDoc (DocItem):
    closeWithLine = 1

    def __init__ (self, title, subitems, className = None):
        DocItem.__init__ (self, title, subitems)
        self.prototypes = []
        if className:
            self.anchor = urllib.quote (className + '::' + title, '')

    def shortDesc (self):
        if self.subitems == None:
            return ''
        result = DocItem.shortDesc (self)
        if not result:
            description = self.subitems.findCategory ('return value')
            if description != None:
                result = 'returns ' + description.shortDesc ()
            else:
                result = ''
        return result

    def genSpecificHTML (self, htmlstream):
        self.genPrototypes (htmlstream)
        self.genSubitems (htmlstream, ['prototypes'])

    def genPrototypes (self, htmlstream):
        prototypes = self.prototypes
        if prototypes:
            htmlstream.pushTags ('pre', 'code')
            source = string.join (prototypes, '\n\n')
            source = _cColorizer.convertString (source)
            htmlstream.write (source)
            htmlstream.popTag (2)

    def readSource (self, source, pos, controller):
        count = self.prototypeCount ()
        nextPos = self.readPrototypes (source, count, pos, controller)
        return nextPos

    def prototypeCount (self):
        entry = self.subitems.findCategory ('prototypes')
        if entry != None:
            result = int (entry.count)
        else:
            result = 1
        return result

    def readPrototypes (self, source, prototypeCount, startPos, controller):
        prototypes = []
        for i in xrange (prototypeCount):
            endPos = string.index (source, ';', startPos)
            prototype = source [startPos:endPos + 1]
            bracePos = string.find (prototype, '{')
            if bracePos != -1:
                prototype = prototype [:bracePos] + ';'
                endPos = self.skipInline (source, startPos + bracePos)
            protoLen = len (prototype)
            prototype = string.expandtabs (prototype, controller.tabSize)
            if len (prototype) > protoLen:
                controller.tabWarning ()
            prototype = removeIndentFromSource (prototype, None)
            prototypes.append (prototype)
            startPos = endPos + 1
        self.prototypes = prototypes
        if strict:
            self.checkPrototypes (prototypes)
        return startPos

    def skipInline (self, source, braceInPos):
        nesting = 1
        braceOutPos = findOrSkip (source, '}', braceInPos + 1)
        braceInPos = findOrSkip (source, '{', braceInPos + 1)
        while nesting > 0:
            if (braceInPos < braceOutPos):
                nesting = nesting + 1
                braceInPos = findOrSkip (source, '{', braceInPos + 1)
            else:
                nesting = nesting - 1
                nextPos = braceOutPos + 1
                braceOutPos = findOrSkip (source, '}', braceOutPos + 1)
        # search for ';', consume if only blanks between
        nextSemikolon = findOrSkip (source, ';', braceOutPos)
        if len (string.strip (source [braceOutPos:nextSemikolon])) == 0:
            nextPos = nextSemikolon + 1
        return nextPos

    def checkPrototypes (self, prototypes):
        if not prototypes:
            return
        parser = CppParser ()
        funcName, paramNames = parser.parseFunctionDeclaration (prototypes [0])
        className = self.acquire ('className', '')
        for prototype in prototypes [1:]:
            nextName, nextParams = parser.parseFunctionDeclaration (prototype)
            if funcName != nextName:
                _warn ('Multiple functions for comment: %s, %s'
                    % (funcName, nextName))
            for paramName in nextParams:
                if not (paramName in paramNames):
                    paramNames.append (paramName)
        if (funcName != self.title) and not (self.title in ['constructor', 'destructor']):
            _warn ('prototype %s::%s must match comment %s'
                % (className, funcName, self.title))
        commentedNames =self.getCommentedNames ()
        # check all parameters are documented
        for declared in paramNames:
            if not (declared in commentedNames):
                _warn ('Function %s::%s: Parameter %s not documented'
                    % (className, self.title, declared))
        # check all documented parameters are used
        for commented in commentedNames:
            if not (commented in paramNames):
                _warn ('Function %s::%s: Parameter %s not used'
                    % (className, self.title, commented))
        # not implemented: check for order of parameters

    def getCommentedNames (self):
        argumentPart = self.subitems.findCategory ('arguments')
        if not argumentPart:
            return []
        names = argumentPart.argumentNames ()
        return names

    def addToIndex (self, weaver):
        className = self.acquire ('className', None)
        if className:
            weaver.addMethod (self.title, className)
        else:
            weaver.addRoutine (self.title)

class DeclarationDoc (DocItem):
    closeWithLine = 1
    endDeclRE = regex.compile ('/[*]!\([^*]*\)[*]/')

    def __init__ (self, title, subitems):
        DocItem.__init__ (self, title, subitems)
        self.source = None

    def genSpecificHTML (self, htmlstream):
        if self.source:
            self.genCode (htmlstream)
        self.genSubitems (htmlstream, ['source'])

    def genCode (self, htmlstream):
        htmlstream.pushTags ('pre', 'code')
        source = _cColorizer.convertString (self.source)
        htmlstream.write (source)
        htmlstream.popTag (2)

    def readSource (self, source, pos, controller):
        re = self.endDeclRE
        commentStart = re.search (source, pos)
        if commentStart == -1:
            self.source = 'No declarations found!'
            controller.warning ('missing end of declaration')
            return pos
        content = re.group (1)
        if string.find (string.lower (content), 'enddecl') != -1:
            self.source = source [pos:commentStart]
            nextPos = re.regs [0][1]
        else:
            self.pos = 'No declarations found!'
            nextPos = commentStart
        return nextPos

_categoryRE = regex.compile ('[ \n]*\(.*\)\([a-zA-Z]+\):')

_blankRE = regex.compile (' *')
_commentRE = regex.compile (' *\*+* *')

def countSkip (lines, patterns, ignoreFirstLine = 1):
    result = 1000
    trim = string.strip
    if ignoreFirstLine:
        bottomLines = lines [1:]
    else:
        bottomLines = lines
    for re in patterns:
        for line in bottomLines:
            if len (trim(line)) == 0:
                continue
            matchSize = re.match (line)
            if (matchSize >= 0) and (matchSize < result):
                result = matchSize
    if result == 1000:
        result = 0
    return result

def stripFirstLine (line, leadingSkip):
    leftTrimmed = string.lstrip (line)
    leadingBlanks = len (line) - len (leftTrimmed)
    if leadingBlanks < leadingSkip:
        result = leftTrimmed
    else:
        result = line [leadingSkip:]
    return result

def stripLead (lines, leadingSkip):
    if leadingSkip == 0:
        lines [0] = string.strip (lines [0])
        return lines
    result = [stripFirstLine (lines [0], leadingSkip)]
    for line in lines [1:]:
        result.append (line [leadingSkip:])
    return result

def removeIndentFromLines (lines, ignoreFirstLine = 1):
    leadingSkip = countSkip (lines, [_blankRE, _commentRE], ignoreFirstLine)
    lines = stripLead (lines, leadingSkip)
    return lines

def removeIndentFromSource (source, ignoreFirstLine = 1):
    lines = string.split (source, '\n')
    lines = removeIndentFromLines (lines, ignoreFirstLine)
    return string.join (lines, '\n')

_decoRE = regex.compile (' *[=*-]+ *$')

def removeDecoLines (lines):
    result = filter (lambda line: _decoRE.match (line) == -1, lines)
    return result

_categoryRE = regex.compile ('[a-zA-Z][^:]*:')

def breakSubitems (lines):
    result = []
    currentLines = []
    re = _categoryRE
    for line in lines:
        matchSize = re.match (line)
        if matchSize == -1:
            currentLines.append (line)
        else:
            category = string.strip (line [:matchSize - 1])
            currentLines = [line [matchSize + 1:]]
            result.append ((category, currentLines))
    return result

class SubItems:
    def __init__ (self):
        self.__items = []

    def items (self):
        return self.__items

    def addItem (self, category, value):
        self.__items.append ((category, value))

    def insertItem (self, index, category, value):
        self.__items.insert (index, (category, value))

    def findCategory (self, categoryParm, default = None):
        for category, value in self.__items:
            if categoryParm == string.lower (category):
                return value
        return default

class DocEntry:
    def shortForm (self):
        return None


class SimpleEntry (DocEntry):
    def __init__ (self, lines):
        self.lines = lines

    def genHTML (self, htmlstream):
        for line in self.lines:
            htmlstream.write (line)
            htmlstream.lineBreak ()

    def text (self):
        return string.join (self.lines, '\n')

class ResponsibleEntry (DocEntry):
    def __init__ (self, lines):
        self.lines = lines

    def genHTML (self, htmlstream):
        for line in self.lines [:-1]:
            htmlstream.write (line)
            htmlstream.lineBreak ()

class PrototypesEntry (DocEntry):
    def __init__ (self, lines):
        self.count = int (string.strip (lines [0]))

class SeeAlsoEntry (DocEntry):
    def __init__ (self, lines):
        self.entries = lines

    def genHTML (self, htmlstream):
        for entry in self.entries:
            entry = string.strip (entry)
            if entry:
                htmlstream.write (href (entry) + '<BR>\n')

_argdescRE = regex.compile (' *\([a-zA-Z][a-zA-Z0-9_]*\) +\(\[[^]]+]\)\(.*\)')
_varargdescRE = regex.compile (' *\([.][.][.]\) +\(\[[^]]+]\)\(.*\)')

class ArgumentsEntry (DocEntry):
    def __init__ (self, lines):
        args = []
        re = _argdescRE
        vre = _varargdescRE
        argname = ''
        direction = ''
        currentDesc = ''
        for line in lines:
            for matchRE in [re, vre]:
                matchSize = matchRE.match (line)
                if matchSize != -1:
                    break
            if matchSize == -1:
                currentDesc = currentDesc + line
            else:
                args.append ((argname, direction, currentDesc))
                argname, direction, currentDesc = matchRE.group (1, 2, 3)
        args.append ((argname, direction, currentDesc))
        self.args = args [1:]

    def genHTML (self, htmlstream):
        if self.args:
            htmlstream.pushTag ('dl')
            for argname, direction, argdesc in self.args:
                htmlstream.tag ('dt')
                htmlstream.write (tagged ('code', argname))
                htmlstream.write (' ' + tagged ('i', direction))
                htmlstream.tag ('dd')
                htmlstream.write (argdesc)
            htmlstream.popTag ()
            htmlstream.lineBreak ()
        else:
            htmlstream.write ('none')
            htmlstream.lineBreak ()

    def argumentNames (self):
        return map (lambda t: t[0], self.args)

_listRE = regex.compile (' *[-*] *')

class DescriptionEntry (DocEntry):
    def __init__ (self, lines):
        self.breakParagraphs (lines)

    def breakParagraphs (self, lines):
        paragraphs = []
        inSpace = 1
        strip = string.strip
        currentParagraph = None
        for line in lines:
            bareLine = strip(line)
            if len (bareLine) == 0:
                if inSpace:
                    pass    # ignore
                else:
                    if currentParagraph != None:
                        paragraphs.append (self.createParagraph (currentParagraph))
                    currentParagraph = None
                    inSpace = 1
            else:
                if inSpace:
                    currentParagraph = [line]
                    inSpace = None
                else:
                    currentParagraph.append (line)
        if currentParagraph != None:
            paragraphs.append (self.createParagraph (currentParagraph))
        self.paragraphs = paragraphs

    def createParagraph (self, lines):
        re = _listRE
        if re.match (lines [0]) != -1:
            result = SimpleList (lines, re)
        else:
            result = SimpleParagraph (lines)
        return result

    def shortDesc (self):
        paragraphs = self.paragraphs
        if len (paragraphs) > 0:
            result = paragraphs [0].text
        else:
            result = ''
        return result

    def genHTML (self, htmlstream):
        for paragraph in self.paragraphs:
            paragraph.genHTML (htmlstream)


class ReleaseEntry (DocEntry):
    def __init__ (self, release):
        self.release = release

    def genHTML (self, htmlstream):
        htmlstream.write (self.release)

    def text (self):
        return self.release

class SimpleParagraph:
    def __init__ (self, lines):
        self.text = string.join (lines, '\n')

    def genHTML (self, htmlstream):
        htmlstream.write (self.text)
        htmlstream.tag ('p')

    def genLastHTML (self, htmlstream):
        htmlstream.write (self.text)

class SimpleList:
    text = ''
    def __init__ (self, lines, re):
        entries = []
        currentEntry = ''
        for line in lines:
            matchSize = re.match (line)
            if matchSize == -1:
                currentEntry = currentEntry + ' ' + line
            else:
                entries.append (currentEntry)
                currentEntry = line [matchSize:]
        entries.append (currentEntry)
        self.entries = entries [1:]

    def genHTML (self, htmlstream):
        htmlstream.pushTag ('ul')
        for entry in self.entries:
            htmlstream.pseudoBreak ()
            htmlstream.tag ('li')
            htmlstream.write (entry)
        htmlstream.pseudoBreak ()
        htmlstream.popTag ()

    genLastHTML = genHTML

class CopyrightEntry (SimpleEntry):
    def __init__ (self, text):
        self.text = string.join (text)

    def genHTML (self, htmlstream):
        htmlstream.write ('&copy; ' + self.text + '<br>')

def qbullet (text):
    return ('<IMG SRC="/Images/qbullets/%s.gif" ALT="%s" WIDTH="28 HEIGHT="8">'
        % (text, text))

def asText (text):
    return '<FONT COLOR="red">%s</FONT>' % text

emitter = asText

oneDay = 24 * 60 * 60
now = time.time ()

def parseDateString (text):
  # try RFC 822 date format.
  timeTuple = rfc822.parsedate(text)
  if timeTuple == None:
      timeList = [0] * 9
      if string.find(text, '-') != -1:
          # try ISO date format: YYYY-MM-DD.
          items = string.split ( text, '-' )
          for i in range (len (items)):
              try:
                  timeList [i] = int (items [i])
              except ValueError:
                  return 0
      else:
          items = string.split (text, '.')
          if len (items) != 3:
              return 0
          for i in (2, 1, 0):
              try:
                  # change day.month.year to (year, month, day, ...)
                  timeList [2 - i] = int (items [i])
              except ValueError:
                  return 0
      timeTuple = tuple (timeList)
  return time.mktime (timeTuple), timeTuple

class DatedEntry (DocEntry):
    diffDays = 30

    def __init__ (self, lines):
        text = string.join (lines)
        self.text = text
        self.date, self.dateTuple = parseDateString (text)

    def shortForm (self):
        if now - self.date < self.diffDays * oneDay:
            return emitter (self.kind)
        else:
            return None

    def genHTML (self, htmlstream):
        htmlstream.write (time.strftime ("%Y-%m-%d<BR>\n", self.dateTuple))

class NewEntry (DatedEntry):
    kind = 'New!'

class UpdatedEntry (DatedEntry):
    kind = 'Updated!'

class ObsoleteEntry (DescriptionEntry):
    def shortForm (self):
        return asText ('Obsolete!')

class CodeEntry (SimpleEntry):
    def __init__ (self, text):
        source = string.join (text, '\n')
        self.source = removeIndentFromSource (source, None)

    def genHTML (self, htmlstream):
        htmlstream.pushTags ('pre', 'code')
        source = _cColorizer.convertString (self.source)
        htmlstream.write (source)
        #for line in self.lines:
        #    htmlstream.write (line)
        #    htmlstream.pseudoBreak ()
        htmlstream.popTag (2)

class SuperclassesEntry (SimpleEntry):
    def __init__ (self, classlist, weaver):
        SimpleEntry.__init__ (self, '')
        self.classlist = classlist
        self.weaver = weaver

    def genHTML (self, htmlstream):
        for name, visibility, isTemplate in self.classlist:
            if visibility != 'private':
                classLink = self.weaver.getClassLink (name)
                htmlstream.write (classLink + '<BR>\n')

class DefListEntry (SimpleEntry):
    def __init__ (self, kind, text):
        SimpleEntry.__init__ (self, kind)
        self.parseText (text)
        self.weaver = None

    excRE = re.compile ('^([a-z0-9_]+)', re.M | re.I)

    def parseText (self, text):
        strip = string.strip
        text = string.join (removeIndentFromLines (text, 1), '\n')
        parts = self.excRE.split (text)
        items = []
        if not strip (parts [0]):
            del parts [0]
        for i in xrange (0, len (parts), 2):
            items.append ((parts[i], strip (parts [i + 1])))
        self.items = items

    def genHTML (self, htmlstream):
        htmlstream.pushTag ('dl')
        for name, description in self.items:
            htmlstream.pseudoBreak ()
            htmlstream.tag ('dt')
            htmlstream.write (self.formatDefinition (name))
            htmlstream.tag ('dd')
            if description:
                htmlstream.write (description)
        htmlstream.pseudoBreak ()
        htmlstream.popTag ()
        htmlstream.pseudoBreak ()

class ExceptionsEntry (SimpleEntry):
    def __init__ (self, text):
        SimpleEntry.__init__ (self, 'exceptions')
        self.parseText (text)
        self.weaver = None

    excRE = re.compile ('^([a-z0-9_]+)', re.M | re.I)

    def parseText (self, text):
        strip = string.strip
        text = string.join (removeIndentFromLines (text, 1), '\n')
        parts = self.excRE.split (text)
        exceptions = []
        if not strip (parts [0]):
            del parts [0]
        for i in xrange (0, len (parts), 2):
            exceptions.append ((parts[i], strip (parts [i + 1])))
        self.exceptionList = exceptions

    def genHTML (self, htmlstream):
        htmlstream.pushTag ('ul')
        for classname, description in self.exceptionList:
            htmlstream.pseudoBreak ()
            htmlstream.tag ('li')
            if self.weaver:
                classLink = self.weaver.getClassLink (classname)
            else:
                classLink = classname
            htmlstream.write (tagged ('b', classname))
            if description:
                htmlstream.write (' - ' + description)
        htmlstream.pseudoBreak ()
        htmlstream.popTag ()
        htmlstream.pseudoBreak ()

class TemplateArgsEntry (DefListEntry):
    def __init__ (self, lines):
        DefListEntry.__init__ (self, 'template arguments', lines)

    def formatDefinition (self, name):
        return name

_categoryHandler = {
        'description': DescriptionEntry,
        'htmldescription': DescriptionEntry,
        'prototypes' : PrototypesEntry,
        'return value': DescriptionEntry,
        'returns': DescriptionEntry,
        'arguments': ArgumentsEntry,
        'see also': SeeAlsoEntry,
        'see': SeeAlsoEntry,
        'responsible': ResponsibleEntry,
        'copyright': CopyrightEntry,
        'new': NewEntry,
        'created': NewEntry,
        'updated': UpdatedEntry,
        'obsolete': ObsoleteEntry,
        'code': CodeEntry,
        'example': CodeEntry,
        'exceptions': ExceptionsEntry,
        'template arguments': TemplateArgsEntry,
        }

def findCategoryHandler (category):
    handler = _categoryHandler.get (string.lower(category), SimpleEntry)
    return handler

_htmlAllowed = ['htmldescription', 'code', 'example']

def parseAttributes (attributes, default):
    if not attributes:
        return default
    result = []
    find = string.find
    for attribute in attributes:
        pos = find (attribute, '=')
        if pos == -1:
            attrTuple = (attribute, None)
        else:
            attrTuple = (attribute [:pos], attribute [pos+1:])
        result.append (attrTuple)
    return result

class HighlightStream (HTMLStream):
    def __init__ (self, fname, title = None, bodyList = None, specialIdentifier = None):
        stream = open (fname, "w")
        self.changeRE = re.compile ('\\b(' + string.join (specialIdentifier, '|') + ')\\b')
        HTMLStream.__init__ (self, stream, title, bodyList)

    def write (self, text):
        text = self.changeRE.sub (r'<strong>\1</strong>', text)
        HTMLStream.write (self, text)

_cColorizer = c2html.C2html ()

class CppDocError:
    def __init__ (self):
        self.excInfo = sys.exc_info ()

    def dump (self, sourcefile, lineno, text, stream):
        if fullBacktrace and (self.excInfo [0] != None):
            apply (traceback.print_exception, self.excInfo)
        stream.write ("%s (near line %d): %s\n"
            % (sourcefile, lineno, text))


class ParseError (CppDocError):
    def __init__ (self, text, pos = 0):
        CppDocError.__init__ (self)
        self.text = string.strip (text)
        self.pos = pos

    def dump (self, sourcefile, lineno, stream = sys.stderr):
        msg = "Parse error in '%s'" % self.text
        CppDocError.dump (self, sourcefile, lineno, msg, stream)

class GroupMismatchError (CppDocError):
    def __init__ (self, category, groupTitle, closeTitle):
        CppDocError.__init__ (self)
        self.category = category
        self.groupTitle = groupTitle
        self.closeTitle = closeTitle

    def dump (self, sourcefile, lineno, stream = sys.stderr):
        msg = "%s mismatch (got '%s', expected '%s')" % (
            self.category, self.closeTitle, self.groupTitle)
        CppDocError.dump (self, sourcefile, lineno, msg, stream)

class InvalidBlockError (CppDocError):
    def __init__ (self, lines):
        CppDocError.__init__ (self)
        self.text = string.strip (firstTextLine (lines))

    def dump (self, sourcefile, lineno, stream = sys.stderr):
        msg = "Parse error in '%s' (missing ':'?)" % self.text
        CppDocError.dump (self, sourcefile, lineno, msg, stream)

def checkForGroupMismatch (category, groupTitle, closeTitle):
    closeTitle = string.strip (closeTitle)
    if closeTitle == '':
        return
    if string.lower (category) == 'endclass':
        closeTitle = 'Class ' + closeTitle
    if groupTitle != closeTitle:
        raise GroupMismatchError (category, groupTitle, closeTitle)

def findOrSkip (source, pattern, start):
    result = string.find (source, pattern, start)
    if result == -1:
        result = len (source)
    return result

def firstTextLine (lines):
    for line in lines:
        if line:
            return line
    return ''

def entityfy (text):
    text = string.replace (text, '&', '&amp;')
    text = string.replace (text, '<', '&lt;')
    return text

def entityfyList (list):
    result = map (entityfy, list)
    return result

_cidentifier = '\([~a-zA-Z_][a-zA-Z0-9_]*\)'
_operatorName = '\(operator *[][()+-*/%&|~^!><=,]+\)'
_operatorNewName = '\(operator *\(new\|delete\) *\([[] *]\)?\)'

class CppParser:
    paramPattern = ' *(\([^)]*\))'
    #funcDclRE = regex.compile (_cidentifier + ' *(\([^]*\))')
    funcDclRE = regex.compile (_cidentifier + ' *(\([^)]*\))')
    operatorDclRE = regex.compile (_operatorName + ' *(\([^)]*\))')
    operatorNewDclRE = regex.compile (_operatorNewName + paramPattern)
    paramRE = regex.compile (_cidentifier + '\( *[[] *[]]\)?\( *=.*\)? *$')

    def parseFunctionDeclaration (self, text):
        for re in [self.funcDclRE, self.operatorDclRE, self.operatorNewDclRE]:
            matchPos = re.search (text)
            if matchPos != -1:
                break
        else:
            return None
        funcName, paramString = re.group (1, 2)
        paramNames = []
        if string.strip (paramString) != '':
            paramStrings = string.split (paramString, ',')
            for str in paramStrings:
                str = string.strip (str)
                if self.paramRE.search (str) != -1:
                    paramNames.append (self.paramRE.group (1))
                else:
                    paramNames.append ('parse error')
        return funcName, paramNames

