## @file
#
# This file produce action class to generate doxygen document for edk2 codebase.
# The action classes are shared by GUI and command line tools.
#
# Copyright (c) 2011 - 2018, Intel Corporation. All rights reserved.
#
# SPDX-License-Identifier: BSD-2-Clause-Patent
from plugins.EdkPlugins.basemodel import doxygen
import os
try:
    import wx
    gInGui = True
except:
    gInGui = False
import re
from plugins.EdkPlugins.edk2.model import inf
from plugins.EdkPlugins.edk2.model import dec
from plugins.EdkPlugins.basemodel.message import *
_ignore_dir = ['.svn', '_svn', 'cvs']
_inf_key_description_mapping_table = {
  'INF_VERSION':'Version of INF file specification',
  #'BASE_NAME':'Module Name',
  'FILE_GUID':'Module Guid',
  'MODULE_TYPE': 'Module Type',
  'VERSION_STRING': 'Module Version',
  'LIBRARY_CLASS': 'Produced Library Class',
  'EFI_SPECIFICATION_VERSION': 'UEFI Specification Version',
  'PI_SPECIFICATION_VERSION': 'PI Specification Version',
  'ENTRY_POINT': 'Module Entry Point Function',
  'CONSTRUCTOR': 'Library Constructor Function'
}
_dec_key_description_mapping_table = {
  'DEC_SPECIFICATION': 'Version of DEC file specification',
  'PACKAGE_GUID': 'Package Guid'
}
class DoxygenAction:
    """This is base class for all doxygen action.
    """
    def __init__(self, doxPath, chmPath, outputPath, projname, mode='html', log=None, verbose=False):
        """Constructor function.
        @param  doxPath         the obosolution path of doxygen execute file.
        @param  outputPath      the obosolution output path.
        @param  log             log function for output message
        """
        self._doxPath       = doxPath
        self._chmPath       = chmPath
        self._outputPath    = outputPath
        self._projname      = projname
        self._configFile    = None          # doxygen config file is used by doxygen exe file
        self._indexPageFile = None          # doxygen page file for index page.
        self._log           = log
        self._mode          = mode
        self._verbose       = verbose
        self._doxygenCallback = None
        self._chmCallback     = None
    def Log(self, message, level='info'):
        if self._log is not None:
            self._log(message, level)
    def IsVerbose(self):
        return self._verbose
    def Generate(self):
        """Generate interface called by outer directly"""
        self.Log(">>>>>> Start generate doxygen document for %s... Zzz....\n" % self._projname)
        # create doxygen config file at first
        self._configFile = doxygen.DoxygenConfigFile()
        self._configFile.SetOutputDir(self._outputPath)
        self._configFile.SetWarningFilePath(os.path.join(self._outputPath, 'warning.txt'))
        if self._mode.lower() == 'html':
            self._configFile.SetHtmlMode()
        else:
            self._configFile.SetChmMode()
        self.Log("    >>>>>> Initialize doxygen config file...Zzz...\n")
        self.InitializeConfigFile()
        self.Log("    >>>>>> Generate doxygen index page file...Zzz...\n")
        indexPagePath = self.GenerateIndexPage()
        if indexPagePath is None:
            self.Log("Fail to generate index page!\n", 'error')
            return False
        else:
            self.Log("Success to create doxygen index page file %s \n" % indexPagePath)
        # Add index page doxygen file to file list.
        self._configFile.AddFile(indexPagePath)
        # save config file to output path
        configFilePath = os.path.join(self._outputPath, self._projname + '.doxygen_config')
        self._configFile.Generate(configFilePath)
        self.Log("    <<<<<< Success Save doxygen config file to %s...\n" % configFilePath)
        # launch doxygen tool to generate document
        if self._doxygenCallback is not None:
            self.Log("    >>>>>> Start doxygen process...Zzz...\n")
            if not self._doxygenCallback(self._doxPath, configFilePath):
                return False
        else:
            self.Log("Fail to create doxygen process!", 'error')
            return False
        return True
    def InitializeConfigFile(self):
        """Initialize config setting for doxygen project. It will be invoked after config file
           object is created. Inherited class should implement it.
        """
    def GenerateIndexPage(self):
        """Generate doxygen index page. Inherited class should implement it."""
        return None
    def RegisterCallbackDoxygenProcess(self, callback):
        self._doxygenCallback = callback
    def RegisterCallbackCHMProcess(self, callback):
        self._chmCallback = callback
class PlatformDocumentAction(DoxygenAction):
    """Generate platform doxygen document, will be implement at future."""
class PackageDocumentAction(DoxygenAction):
    """Generate package reference document"""
    def __init__(self, doxPath, chmPath, outputPath, pObj, mode='html', log=None, arch=None, tooltag=None,
                 macros=[], onlyInclude=False, verbose=False):
        DoxygenAction.__init__(self, doxPath, chmPath, outputPath, pObj.GetName(), mode, log, verbose)
        self._pObj   = pObj
        self._arch   = arch
        self._tooltag = tooltag
        self._macros = macros
        self._onlyIncludeDocument = onlyInclude
    def InitializeConfigFile(self):
        if self._arch == 'IA32':
            self._configFile.AddPreDefined('MDE_CPU_IA32')
        elif self._arch == 'X64':
            self._configFile.AddPreDefined('MDE_CPU_X64')
        elif self._arch == 'IPF':
            self._configFile.AddPreDefined('MDE_CPU_IPF')
        elif self._arch == 'EBC':
            self._configFile.AddPreDefined('MDE_CPU_EBC')
        else:
            self._arch = None
            self._configFile.AddPreDefined('MDE_CPU_IA32')
            self._configFile.AddPreDefined('MDE_CPU_X64')
            self._configFile.AddPreDefined('MDE_CPU_IPF')
            self._configFile.AddPreDefined('MDE_CPU_EBC')
            self._configFile.AddPreDefined('MDE_CPU_ARM')
        for macro in self._macros:
            self._configFile.AddPreDefined(macro)
        namestr = self._pObj.GetName()
        if self._arch is not None:
            namestr += '[%s]' % self._arch
        if self._tooltag is not None:
            namestr += '[%s]' % self._tooltag
        self._configFile.SetProjectName(namestr)
        self._configFile.SetStripPath(self._pObj.GetWorkspace())
        self._configFile.SetProjectVersion(self._pObj.GetFileObj().GetVersion())
        self._configFile.AddPattern('*.decdoxygen')
        if self._tooltag.lower() == 'msft':
            self._configFile.AddPreDefined('_MSC_EXTENSIONS')
        elif self._tooltag.lower() == 'gnu':
            self._configFile.AddPreDefined('__GNUC__')
        elif self._tooltag.lower() == 'intel':
            self._configFile.AddPreDefined('__INTEL_COMPILER')
        else:
            self._tooltag = None
            self._configFile.AddPreDefined('_MSC_EXTENSIONS')
            self._configFile.AddPreDefined('__GNUC__')
            self._configFile.AddPreDefined('__INTEL_COMPILER')
        self._configFile.AddPreDefined('ASM_PFX= ')
        self._configFile.AddPreDefined('OPTIONAL= ')
    def GenerateIndexPage(self):
        """Generate doxygen index page. Inherited class should implement it."""
        fObj   = self._pObj.GetFileObj()
        pdObj  = doxygen.DoxygenFile('%s Package Document' % self._pObj.GetName(),
                                     '%s.decdoxygen' % self._pObj.GetFilename())
        self._configFile.AddFile(pdObj.GetFilename())
        pdObj.AddDescription(fObj.GetFileHeader())
        defSection = fObj.GetSectionByName('defines')[0]
        baseSection = doxygen.Section('PackageBasicInformation', 'Package Basic Information')
        descr = '
'
        for obj in defSection.GetObjects():
            if obj.GetKey() in _dec_key_description_mapping_table.keys():
                descr += ''
                descr += '| %s | ' % _dec_key_description_mapping_table[obj.GetKey()]
                descr += '%s | ' % obj.GetValue()
                descr += '
'
        descr += '
'
        baseSection.AddDescription(descr)
        pdObj.AddSection(baseSection)
        knownIssueSection = doxygen.Section('Known_Issue_section', 'Known Issue')
        knownIssueSection.AddDescription('')
        knownIssueSection.AddDescription('-  OPTIONAL macro for function parameter can not be dealed with doxygen, so it disapear in this document! 
 ')
        knownIssueSection.AddDescription('
')
        pdObj.AddSection(knownIssueSection)
        self.AddAllIncludeFiles(self._pObj, self._configFile)
        pages = self.GenerateIncludesSubPage(self._pObj, self._configFile)
        if len(pages) != 0:
            pdObj.AddPages(pages)
        pages = self.GenerateLibraryClassesSubPage(self._pObj, self._configFile)
        if len(pages) != 0:
            pdObj.AddPages(pages)
        pages = self.GeneratePcdSubPages(self._pObj, self._configFile)
        if len(pages) != 0:
            pdObj.AddPages(pages)
        pages = self.GenerateGuidSubPages(self._pObj, self._configFile)
        if len(pages) != 0:
            pdObj.AddPages(pages)
        pages = self.GeneratePpiSubPages(self._pObj, self._configFile)
        if len(pages) != 0:
            pdObj.AddPages(pages)
        pages = self.GenerateProtocolSubPages(self._pObj, self._configFile)
        if len(pages) != 0:
            pdObj.AddPages(pages)
        if not self._onlyIncludeDocument:
            pdObj.AddPages(self.GenerateModulePages(self._pObj, self._configFile))
        pdObj.Save()
        return pdObj.GetFilename()
    def GenerateIncludesSubPage(self, pObj, configFile):
        # by default add following path as include path to config file
        pkpath = pObj.GetFileObj().GetPackageRootPath()
        configFile.AddIncludePath(os.path.join(pkpath, 'Include'))
        configFile.AddIncludePath(os.path.join(pkpath, 'Include', 'Library'))
        configFile.AddIncludePath(os.path.join(pkpath, 'Include', 'Protocol'))
        configFile.AddIncludePath(os.path.join(pkpath, 'Include', 'Ppi'))
        configFile.AddIncludePath(os.path.join(pkpath, 'Include', 'Guid'))
        configFile.AddIncludePath(os.path.join(pkpath, 'Include', 'IndustryStandard'))
        rootArray = []
        pageRoot = doxygen.Page("Public Includes", "%s_public_includes" % pObj.GetName())
        objs = pObj.GetFileObj().GetSectionObjectsByName('includes')
        if len(objs) == 0: return []
        for obj in objs:
            # Add path to include path
            path = os.path.join(pObj.GetFileObj().GetPackageRootPath(), obj.GetPath())
            configFile.AddIncludePath(path)
            # only list common folder's include file
            if obj.GetArch().lower() != 'common':
                continue
            bNeedAddIncludePage = False
            topPage = doxygen.Page(self._ConvertPathToDoxygen(path, pObj), 'public_include_top')
            topPage.AddDescription('\n')
            for file in os.listdir(path):
                if file.lower() in _ignore_dir: continue
                fullpath = os.path.join(path, file)
                if os.path.isfile(fullpath):
                    self.ProcessSourceFileForInclude(fullpath, pObj, configFile)
                    topPage.AddDescription('-  \link %s\endlink 
 \n' % self._ConvertPathToDoxygen(fullpath, pObj))
                else:
                    if file.lower() in ['library', 'protocol', 'guid', 'ppi', 'ia32', 'x64', 'ipf', 'ebc', 'arm', 'pi', 'uefi', 'aarch64']:
                        continue
                    bNeedAddSubPage = False
                    subpage = doxygen.Page(self._ConvertPathToDoxygen(fullpath, pObj), 'public_include_%s' % file)
                    subpage.AddDescription('\n')
                    for subfile in os.listdir(fullpath):
                        if subfile.lower() in _ignore_dir: continue
                        bNeedAddSubPage = True
                        subfullpath = os.path.join(fullpath, subfile)
                        self.ProcessSourceFileForInclude(subfullpath, pObj, configFile)
                        subpage.AddDescription('-  \link %s \endlink 
 \n' % self._ConvertPathToDoxygen(subfullpath, pObj))
                    subpage.AddDescription('
\n')
                    if bNeedAddSubPage:
                        bNeedAddIncludePage = True
                        pageRoot.AddPage(subpage)
            topPage.AddDescription('
\n')
            if bNeedAddIncludePage:
                pageRoot.AddPage(topPage)
        if pageRoot.GetSubpageCount() != 0:
            return [pageRoot]
        else:
            return []
    def GenerateLibraryClassesSubPage(self, pObj, configFile):
        """
        Generate sub page for library class for package.
        One DEC file maybe contains many library class sections
        for different architecture.
        @param  fObj DEC file object.
        """
        rootArray = []
        pageRoot = doxygen.Page("Library Class", "%s_libraryclass" % pObj.GetName())
        objs = pObj.GetFileObj().GetSectionObjectsByName('libraryclass', self._arch)
        if len(objs) == 0: return []
        if self._arch is not None:
            for obj in objs:
                classPage = doxygen.Page(obj.GetClassName(),
                                         "lc_%s" % obj.GetClassName())
                comments = obj.GetComment()
                if len(comments) != 0:
                    classPage.AddDescription('
\n'.join(comments) + '
\n')
                pageRoot.AddPage(classPage)
                path = os.path.join(pObj.GetFileObj().GetPackageRootPath(), obj.GetHeaderFile())
                path = path[len(pObj.GetWorkspace()) + 1:]
                if len(comments) == 0:
                    classPage.AddDescription('\copydoc %s' % obj.GetHeaderFile())
                section = doxygen.Section('ref', 'Refer to Header File')
                section.AddDescription('\link %s\n' % obj.GetHeaderFile())
                section.AddDescription(' \endlink
\n')
                classPage.AddSection(section)
                fullPath = os.path.join(pObj.GetFileObj().GetPackageRootPath(), obj.GetHeaderFile())
                self.ProcessSourceFileForInclude(fullPath, pObj, configFile)
        else:
            archPageDict = {}
            for obj in objs:
                if obj.GetArch() not in archPageDict.keys():
                    archPageDict[obj.GetArch()] = doxygen.Page(obj.GetArch(),
                                                               'lc_%s' % obj.GetArch())
                    pageRoot.AddPage(archPageDict[obj.GetArch()])
                subArchRoot = archPageDict[obj.GetArch()]
                classPage = doxygen.Page(obj.GetClassName(),
                                         "lc_%s" % obj.GetClassName())
                comments = obj.GetComment()
                if len(comments) != 0:
                    classPage.AddDescription('
\n'.join(comments) + '
\n')
                subArchRoot.AddPage(classPage)
                path = os.path.join(pObj.GetFileObj().GetPackageRootPath(), obj.GetHeaderFile())
                path = path[len(pObj.GetWorkspace()) + 1:]
                if len(comments) == 0:
                    classPage.AddDescription('\copydoc %s
' % obj.GetHeaderFile())
                section = doxygen.Section('ref', 'Refer to Header File')
                section.AddDescription('\link %s\n' % obj.GetHeaderFile())
                section.AddDescription(' \endlink
\n')
                classPage.AddSection(section)
                fullPath = os.path.join(pObj.GetFileObj().GetPackageRootPath(), obj.GetHeaderFile())
                self.ProcessSourceFileForInclude(fullPath, pObj, configFile)
        rootArray.append(pageRoot)
        return rootArray
    def ProcessSourceFileForInclude(self, path, pObj, configFile, infObj=None):
        """
        @param path        the analysising file full path
        @param pObj        package object
        @param configFile  doxygen config file.
        """
        if gInGui:
            wx.Yield()
        if not os.path.exists(path):
            ErrorMsg('Source file path %s does not exist!' % path)
            return
        if configFile.FileExists(path):
            return
        try:
            f = open(path, 'r')
            lines = f.readlines()
            f.close()
        except IOError:
            ErrorMsg('Fail to open file %s' % path)
            return
        configFile.AddFile(path)
        return
        no = 0
        for no in range(len(lines)):
            if len(lines[no].strip()) == 0:
                continue
            if lines[no].strip()[:2] in ['##', '//', '/*', '*/']:
                continue
            index = lines[no].lower().find('include')
            #mo = IncludePattern.finditer(lines[no].lower())
            mo = re.match(r"^#\s*include\s+[<\"]([\\/\w.]+)[>\"]$", lines[no].strip().lower())
            if not mo:
                continue
            mo = re.match(r"^[#\w\s]+[<\"]([\\/\w.]+)[>\"]$", lines[no].strip())
            filePath = mo.groups()[0]
            if filePath is None or len(filePath) == 0:
                continue
            # find header file in module's path firstly.
            fullPath = None
            if os.path.exists(os.path.join(os.path.dirname(path), filePath)):
                # Find the file in current directory
                fullPath = os.path.join(os.path.dirname(path), filePath).replace('\\', '/')
            else:
                # find in depedent package's include path
                incObjs = pObj.GetFileObj().GetSectionObjectsByName('includes')
                for incObj in incObjs:
                    incPath = os.path.join(pObj.GetFileObj().GetPackageRootPath(), incObj.GetPath()).strip()
                    incPath = os.path.realpath(os.path.join(incPath, filePath))
                    if os.path.exists(incPath):
                        fullPath = incPath
                        break
                if infObj is not None:
                    pkgInfObjs = infObj.GetSectionObjectsByName('packages')
                    for obj in  pkgInfObjs:
                        decObj = dec.DECFile(os.path.join(pObj.GetWorkspace(), obj.GetPath()))
                        if not decObj:
                            ErrorMsg ('Fail to create pacakge object for %s' % obj.GetPackageName())
                            continue
                        if not decObj.Parse():
                            ErrorMsg ('Fail to load package object for %s' % obj.GetPackageName())
                            continue
                        incObjs = decObj.GetSectionObjectsByName('includes')
                        for incObj in incObjs:
                            incPath = os.path.join(decObj.GetPackageRootPath(), incObj.GetPath()).replace('\\', '/')
                            if os.path.exists(os.path.join(incPath, filePath)):
                                fullPath = os.path.join(os.path.join(incPath, filePath))
                                break
                        if fullPath is not None:
                            break
            if fullPath is None and self.IsVerbose():
                self.Log('Can not resolve header file %s for file %s in package %s\n' % (filePath, path, pObj.GetFileObj().GetFilename()), 'error')
                return
            else:
                fullPath = fullPath.replace('\\', '/')
                if self.IsVerbose():
                    self.Log('Preprocessing: Add include file %s for file %s\n' % (fullPath, path))
                #LogMsg ('Preprocessing: Add include file %s for file %s' % (fullPath, path))
                self.ProcessSourceFileForInclude(fullPath, pObj, configFile, infObj)
    def AddAllIncludeFiles(self, pObj, configFile):
        objs = pObj.GetFileObj().GetSectionObjectsByName('includes')
        for obj in objs:
            incPath = os.path.join(pObj.GetFileObj().GetPackageRootPath(), obj.GetPath())
            for root, dirs, files in os.walk(incPath):
                for dir in dirs:
                    if dir.lower() in _ignore_dir:
                        dirs.remove(dir)
                for file in files:
                    path = os.path.normpath(os.path.join(root, file))
                    configFile.AddFile(path.replace('/', '\\'))
    def GeneratePcdSubPages(self, pObj, configFile):
        """
        Generate sub pages for package's PCD definition.
        @param pObj         package object
        @param configFile   config file object
        """
        rootArray = []
        objs = pObj.GetFileObj().GetSectionObjectsByName('pcd')
        if len(objs) == 0:
            return []
        pcdRootPage = doxygen.Page('PCD', 'pcd_root_page')
        typeRootPageDict = {}
        typeArchRootPageDict = {}
        for obj in objs:
            if obj.GetPcdType() not in typeRootPageDict.keys():
                typeRootPageDict[obj.GetPcdType()] = doxygen.Page(obj.GetPcdType(), 'pcd_%s_root_page' % obj.GetPcdType())
                pcdRootPage.AddPage(typeRootPageDict[obj.GetPcdType()])
            typeRoot = typeRootPageDict[obj.GetPcdType()]
            if self._arch is not None:
                pcdPage = doxygen.Page('%s' % obj.GetPcdName(),
                                        'pcd_%s_%s_%s' % (obj.GetPcdType(), obj.GetArch(), obj.GetPcdName().split('.')[1]))
                pcdPage.AddDescription('
\n'.join(obj.GetComment()) + '
\n')
                section = doxygen.Section('PCDinformation', 'PCD Information')
                desc  = '
'
                desc += ''
                desc += '| Name | '
                desc += 'Token Space | '
                desc += 'Token number | '
                desc += 'Data Type | '
                desc += 'Default Value | '
                desc += '
'
                desc += ''
                desc += '| %s | ' % obj.GetPcdName().split('.')[1]
                desc += '%s | ' % obj.GetPcdName().split('.')[0]
                desc += '%s | ' % obj.GetPcdToken()
                desc += '%s | ' % obj.GetPcdDataType()
                desc += '%s | ' % obj.GetPcdValue()
                desc += '
'
                desc += '
'
                section.AddDescription(desc)
                pcdPage.AddSection(section)
                typeRoot.AddPage(pcdPage)
            else:
                keystr = obj.GetPcdType() + obj.GetArch()
                if keystr not in typeArchRootPageDict.keys():
                    typeArchRootPage = doxygen.Page(obj.GetArch(), 'pcd_%s_%s_root_page' % (obj.GetPcdType(), obj.GetArch()))
                    typeArchRootPageDict[keystr] = typeArchRootPage
                    typeRoot.AddPage(typeArchRootPage)
                typeArchRoot = typeArchRootPageDict[keystr]
                pcdPage = doxygen.Page('%s' % obj.GetPcdName(),
                                        'pcd_%s_%s_%s' % (obj.GetPcdType(), obj.GetArch(), obj.GetPcdName().split('.')[1]))
                pcdPage.AddDescription('
\n'.join(obj.GetComment()) + '
\n')
                section = doxygen.Section('PCDinformation', 'PCD Information')
                desc  = ''
                desc += ''
                desc += '| Name | '
                desc += 'Token Space | '
                desc += 'Token number | '
                desc += 'Data Type | '
                desc += 'Default Value | '
                desc += '
'
                desc += ''
                desc += '| %s | ' % obj.GetPcdName().split('.')[1]
                desc += '%s | ' % obj.GetPcdName().split('.')[0]
                desc += '%s | ' % obj.GetPcdToken()
                desc += '%s | ' % obj.GetPcdDataType()
                desc += '%s | ' % obj.GetPcdValue()
                desc += '
'
                desc += '
'
                section.AddDescription(desc)
                pcdPage.AddSection(section)
                typeArchRoot.AddPage(pcdPage)
        return [pcdRootPage]
    def _GenerateGuidSubPage(self, pObj, obj, configFile):
        guidPage = doxygen.Page('%s' % obj.GetName(),
                                'guid_%s_%s' % (obj.GetArch(), obj.GetName()))
        comments = obj.GetComment()
        if len(comments) != 0:
            guidPage.AddDescription('
'.join(obj.GetComment()) + '
')
        section = doxygen.Section('BasicGuidInfo', 'GUID Information')
        desc  = ''
        desc += ''
        desc += '| GUID\'s Guid Name | GUID\'s Guid | '
        desc += '
'
        desc += ''
        desc += '| %s | ' % obj.GetName()
        desc += '%s | ' % obj.GetGuid()
        desc += '
'
        desc += '
'
        section.AddDescription(desc)
        guidPage.AddSection(section)
        refFile = self.FindHeaderFileForGuid(pObj, obj.GetName(), configFile)
        if refFile:
            relPath = refFile[len(pObj.GetWorkspace()) + 1:]
            if len(comments) == 0:
                guidPage.AddDescription(' \\copydoc %s 
' % relPath)
            section = doxygen.Section('ref', 'Refer to Header File')
            section.AddDescription('\link %s\n' % relPath)
            section.AddDescription('\endlink\n')
            self.ProcessSourceFileForInclude(refFile, pObj, configFile)
            guidPage.AddSection(section)
        return guidPage
    def GenerateGuidSubPages(self, pObj, configFile):
        """
        Generate sub pages for package's GUID definition.
        @param  pObj            package object
        @param  configFilf      doxygen config file object
        """
        pageRoot = doxygen.Page('GUID', 'guid_root_page')
        objs = pObj.GetFileObj().GetSectionObjectsByName('guids', self._arch)
        if len(objs) == 0: return []
        if self._arch is not None:
            for obj in objs:
                pageRoot.AddPage(self._GenerateGuidSubPage(pObj, obj, configFile))
        else:
            guidArchRootPageDict = {}
            for obj in objs:
                if obj.GetArch() not in guidArchRootPageDict.keys():
                    guidArchRoot = doxygen.Page(obj.GetArch(), 'guid_arch_root_%s' % obj.GetArch())
                    pageRoot.AddPage(guidArchRoot)
                    guidArchRootPageDict[obj.GetArch()] = guidArchRoot
                guidArchRoot = guidArchRootPageDict[obj.GetArch()]
                guidArchRoot.AddPage(self._GenerateGuidSubPage(pObj, obj, configFile))
        return [pageRoot]
    def _GeneratePpiSubPage(self, pObj, obj, configFile):
        guidPage = doxygen.Page(obj.GetName(), 'ppi_page_%s' % obj.GetName())
        comments = obj.GetComment()
        if len(comments) != 0:
            guidPage.AddDescription('
'.join(obj.GetComment()) + '
')
        section = doxygen.Section('BasicPpiInfo', 'PPI Information')
        desc  = ''
        desc += ''
        desc += '| PPI\'s Guid Name | PPI\'s Guid | '
        desc += '
'
        desc += ''
        desc += '| %s | ' % obj.GetName()
        desc += '%s | ' % obj.GetGuid()
        desc += '
'
        desc += '
'
        section.AddDescription(desc)
        guidPage.AddSection(section)
        refFile = self.FindHeaderFileForGuid(pObj, obj.GetName(), configFile)
        if refFile:
            relPath = refFile[len(pObj.GetWorkspace()) + 1:]
            if len(comments) == 0:
                guidPage.AddDescription(' \\copydoc %s 
' % relPath)
            section = doxygen.Section('ref', 'Refer to Header File')
            section.AddDescription('\link %s\n' % relPath)
            section.AddDescription('\endlink\n')
            self.ProcessSourceFileForInclude(refFile, pObj, configFile)
            guidPage.AddSection(section)
        return guidPage
    def GeneratePpiSubPages(self, pObj, configFile):
        """
        Generate sub pages for package's GUID definition.
        @param  pObj            package object
        @param  configFilf      doxygen config file object
        """
        pageRoot = doxygen.Page('PPI', 'ppi_root_page')
        objs = pObj.GetFileObj().GetSectionObjectsByName('ppis', self._arch)
        if len(objs) == 0: return []
        if self._arch is not None:
            for obj in objs:
                pageRoot.AddPage(self._GeneratePpiSubPage(pObj, obj, configFile))
        else:
            guidArchRootPageDict = {}
            for obj in objs:
                if obj.GetArch() not in guidArchRootPageDict.keys():
                    guidArchRoot = doxygen.Page(obj.GetArch(), 'ppi_arch_root_%s' % obj.GetArch())
                    pageRoot.AddPage(guidArchRoot)
                    guidArchRootPageDict[obj.GetArch()] = guidArchRoot
                guidArchRoot = guidArchRootPageDict[obj.GetArch()]
                guidArchRoot.AddPage(self._GeneratePpiSubPage(pObj, obj, configFile))
        return [pageRoot]
    def _GenerateProtocolSubPage(self, pObj, obj, configFile):
        guidPage = doxygen.Page(obj.GetName(), 'protocol_page_%s' % obj.GetName())
        comments = obj.GetComment()
        if len(comments) != 0:
            guidPage.AddDescription('
'.join(obj.GetComment()) + '
')
        section = doxygen.Section('BasicProtocolInfo', 'PROTOCOL Information')
        desc  = ''
        desc += ''
        desc += '| PROTOCOL\'s Guid Name | PROTOCOL\'s Guid | '
        desc += '
'
        desc += ''
        desc += '| %s | ' % obj.GetName()
        desc += '%s | ' % obj.GetGuid()
        desc += '
'
        desc += '
'
        section.AddDescription(desc)
        guidPage.AddSection(section)
        refFile = self.FindHeaderFileForGuid(pObj, obj.GetName(), configFile)
        if refFile:
            relPath = refFile[len(pObj.GetWorkspace()) + 1:]
            if len(comments) == 0:
                guidPage.AddDescription(' \\copydoc %s 
' % relPath)
            section = doxygen.Section('ref', 'Refer to Header File')
            section.AddDescription('\link %s\n' % relPath)
            section.AddDescription('\endlink\n')
            self.ProcessSourceFileForInclude(refFile, pObj, configFile)
            guidPage.AddSection(section)
        return guidPage
    def GenerateProtocolSubPages(self, pObj, configFile):
        """
        Generate sub pages for package's GUID definition.
        @param  pObj            package object
        @param  configFilf      doxygen config file object
        """
        pageRoot = doxygen.Page('PROTOCOL', 'protocol_root_page')
        objs = pObj.GetFileObj().GetSectionObjectsByName('protocols', self._arch)
        if len(objs) == 0: return []
        if self._arch is not None:
            for obj in objs:
                pageRoot.AddPage(self._GenerateProtocolSubPage(pObj, obj, configFile))
        else:
            guidArchRootPageDict = {}
            for obj in objs:
                if obj.GetArch() not in guidArchRootPageDict.keys():
                    guidArchRoot = doxygen.Page(obj.GetArch(), 'protocol_arch_root_%s' % obj.GetArch())
                    pageRoot.AddPage(guidArchRoot)
                    guidArchRootPageDict[obj.GetArch()] = guidArchRoot
                guidArchRoot = guidArchRootPageDict[obj.GetArch()]
                guidArchRoot.AddPage(self._GenerateProtocolSubPage(pObj, obj, configFile))
        return [pageRoot]
    def FindHeaderFileForGuid(self, pObj, name, configFile):
        """
        For declaration header file for GUID/PPI/Protocol.
        @param pObj         package object
        @param name         guid/ppi/protocol's name
        @param configFile   config file object
        @return full path of header file and None if not found.
        """
        startPath  = pObj.GetFileObj().GetPackageRootPath()
        incPath    = os.path.join(startPath, 'Include').replace('\\', '/')
        # if /include exist, then search header under it.
        if os.path.exists(incPath):
            startPath = incPath
        for root, dirs, files in os.walk(startPath):
            for dir in dirs:
                if dir.lower() in _ignore_dir:
                    dirs.remove(dir)
            for file in files:
                fPath = os.path.join(root, file)
                if not IsCHeaderFile(fPath):
                    continue
                try:
                    f = open(fPath, 'r')
                    lines = f.readlines()
                    f.close()
                except IOError:
                    self.Log('Fail to open file %s\n' % fPath)
                    continue
                for line in lines:
                    if line.find(name) != -1 and \
                       line.find('extern') != -1:
                        return fPath.replace('\\', '/')
        return None
    def GetPackageModuleList(self, pObj):
        """
        Get all module's INF path under package's root path
        @param     pObj  package object
        @return    arrary of INF full path
        """
        mArray = []
        packPath = pObj.GetFileObj().GetPackageRootPath()
        if not os.path.exists:
            return None
        for root, dirs, files in os.walk(packPath):
            for dir in dirs:
                if dir.lower() in _ignore_dir:
                    dirs.remove(dir)
            for file in files:
                if CheckPathPostfix(file, 'inf'):
                    fPath = os.path.join(root, file).replace('\\', '/')
                    mArray.append(fPath)
        return mArray
    def GenerateModulePages(self, pObj, configFile):
        """
        Generate sub pages for package's module which is under the package
        root directory.
        @param  pObj            package object
        @param  configFilf      doxygen config file object
        """
        infList = self.GetPackageModuleList(pObj)
        rootPages = []
        libObjs = []
        modObjs = []
        for infpath in infList:
            infObj = inf.INFFile(infpath)
            #infObj = INFFileObject.INFFile (pObj.GetWorkspacePath(),
            #                                inf)
            if not infObj:
                self.Log('Fail create INF object for %s' % inf)
                continue
            if not infObj.Parse():
                self.Log('Fail to load INF file %s' % inf)
                continue
            if infObj.GetProduceLibraryClass() is not None:
                libObjs.append(infObj)
            else:
                modObjs.append(infObj)
        if len(libObjs) != 0:
            libRootPage = doxygen.Page('Libraries', 'lib_root_page')
            rootPages.append(libRootPage)
            for libInf in libObjs:
                libRootPage.AddPage(self.GenerateModulePage(pObj, libInf, configFile, True))
        if len(modObjs) != 0:
            modRootPage = doxygen.Page('Modules', 'module_root_page')
            rootPages.append(modRootPage)
            for modInf in modObjs:
                modRootPage.AddPage(self.GenerateModulePage(pObj, modInf, configFile, False))
        return rootPages
    def GenerateModulePage(self, pObj, infObj, configFile, isLib):
        """
        Generate page for a module/library.
        @param infObj     INF file object for module/library
        @param configFile doxygen config file object
        @param isLib      Whether this module is library
        @param module doxygen page object
        """
        workspace = pObj.GetWorkspace()
        refDecObjs = []
        for obj in  infObj.GetSectionObjectsByName('packages'):
            decObj = dec.DECFile(os.path.join(workspace, obj.GetPath()))
            if not decObj:
                ErrorMsg ('Fail to create pacakge object for %s' % obj.GetPackageName())
                continue
            if not decObj.Parse():
                ErrorMsg ('Fail to load package object for %s' % obj.GetPackageName())
                continue
            refDecObjs.append(decObj)
        modPage = doxygen.Page('%s' % infObj.GetBaseName(),
                               'module_%s' % infObj.GetBaseName())
        modPage.AddDescription(infObj.GetFileHeader())
        basicInfSection = doxygen.Section('BasicModuleInformation', 'Basic Module Information')
        desc = ""
        for obj in infObj.GetSectionObjectsByName('defines'):
            key = obj.GetKey()
            value = obj.GetValue()
            if key not in _inf_key_description_mapping_table.keys(): continue
            if key == 'LIBRARY_CLASS' and value.find('|') != -1:
                clsname, types = value.split('|')
                desc += ''
                desc += '| %s | ' % _inf_key_description_mapping_table[key]
                desc += '%s | ' % clsname
                desc += '
'
                desc += ''
                desc += '| Supported Module Types | '
                desc += '%s | ' % types
                desc += '
'
            else:
                desc += ''
                desc += '| %s | ' % _inf_key_description_mapping_table[key]
                if key == 'EFI_SPECIFICATION_VERSION' and value == '0x00020000':
                    value = '2.0'
                desc += '%s | ' % value
                desc += '
'
        desc += '
'
        basicInfSection.AddDescription(desc)
        modPage.AddSection(basicInfSection)
        # Add protocol section
        data  = []
        for obj in infObj.GetSectionObjectsByName('pcd', self._arch):
            data.append(obj.GetPcdName().strip())
        if len(data) != 0:
            s = doxygen.Section('Pcds', 'Pcds')
            desc = ""
            desc += '| PCD Name | TokenSpace | Package | 
'
            for item in data:
                desc += ''
                desc += '| %s | ' % item.split('.')[1]
                desc += '%s | ' % item.split('.')[0]
                pkgbasename = self.SearchPcdPackage(item, workspace, refDecObjs)
                desc += '%s | ' % pkgbasename
                desc += '
'
            desc += "
"
            s.AddDescription(desc)
            modPage.AddSection(s)
        # Add protocol section
        #sects = infObj.GetSectionByString('protocol')
        data  = []
        #for sect in sects:
        for obj in infObj.GetSectionObjectsByName('protocol', self._arch):
            data.append(obj.GetName().strip())
        if len(data) != 0:
            s = doxygen.Section('Protocols', 'Protocols')
            desc = ""
            desc += '| Name | Package | 
'
            for item in data:
                desc += ''
                desc += '| %s | ' % item
                pkgbasename = self.SearchProtocolPackage(item, workspace, refDecObjs)
                desc += '%s | ' % pkgbasename
                desc += '
'
            desc += "
"
            s.AddDescription(desc)
            modPage.AddSection(s)
        # Add ppi section
        #sects = infObj.GetSectionByString('ppi')
        data  = []
        #for sect in sects:
        for obj in infObj.GetSectionObjectsByName('ppi', self._arch):
            data.append(obj.GetName().strip())
        if len(data) != 0:
            s = doxygen.Section('Ppis', 'Ppis')
            desc = ""
            desc += '| Name | Package | 
'
            for item in data:
                desc += ''
                desc += '| %s | ' % item
                pkgbasename = self.SearchPpiPackage(item, workspace, refDecObjs)
                desc += '%s | ' % pkgbasename
                desc += '
'
            desc += "
"
            s.AddDescription(desc)
            modPage.AddSection(s)
        # Add guid section
        #sects = infObj.GetSectionByString('guid')
        data  = []
        #for sect in sects:
        for obj in infObj.GetSectionObjectsByName('guid', self._arch):
            data.append(obj.GetName().strip())
        if len(data) != 0:
            s = doxygen.Section('Guids', 'Guids')
            desc = ""
            desc += '| Name | Package | 
'
            for item in data:
                desc += ''
                desc += '| %s | ' % item
                pkgbasename = self.SearchGuidPackage(item, workspace, refDecObjs)
                desc += '%s | ' % pkgbasename
                desc += '
'
            desc += "
"
            s.AddDescription(desc)
            modPage.AddSection(s)
        section = doxygen.Section('LibraryClasses', 'Library Classes')
        desc = ""
        desc += '| Name | Type | Package | Header File | 
'
        if isLib:
            desc += ''
            desc += '| %s | ' % infObj.GetProduceLibraryClass()
            desc += 'Produce | '
            try:
                pkgname, hPath = self.SearchLibraryClassHeaderFile(infObj.GetProduceLibraryClass(),
                                                              workspace,
                                                              refDecObjs)
            except:
                self.Log ('fail to get package header file for lib class %s' % infObj.GetProduceLibraryClass())
                pkgname = 'NULL'
                hPath   = 'NULL'
            desc += '%s | ' % pkgname
            if hPath != "NULL":
                #desc += '\link %s \endlink | ' % hPath
                desc += '%s | ' % hPath
            else:
                desc += '%s | ' % hPath
            desc += '
'
        for lcObj in infObj.GetSectionObjectsByName('libraryclasses', self._arch):
            desc += ''
            desc += '| %s | ' % lcObj.GetClass()
            retarr = self.SearchLibraryClassHeaderFile(lcObj.GetClass(),
                                                       workspace,
                                                       refDecObjs)
            if retarr is not None:
                pkgname, hPath = retarr
            else:
                self.Log('Fail find the library class %s definition from module %s dependent package!' % (lcObj.GetClass(), infObj.GetFilename()), 'error')
                pkgname = 'NULL'
                hPath   = 'NULL'
            desc += 'Consume | '
            desc += '%s | ' % pkgname
            desc += '%s | ' % hPath
            desc += '
'
        desc += "
"
        section.AddDescription(desc)
        modPage.AddSection(section)
        section = doxygen.Section('SourceFiles', 'Source Files')
        section.AddDescription('\n')
        for obj in infObj.GetSourceObjects(self._arch, self._tooltag):
            sPath = infObj.GetModuleRootPath()
            sPath = os.path.join(sPath, obj.GetSourcePath()).replace('\\', '/').strip()
            if sPath.lower().endswith('.uni') or sPath.lower().endswith('.s') or sPath.lower().endswith('.asm') or sPath.lower().endswith('.nasm'):
                newPath = self.TranslateUniFile(sPath)
                configFile.AddFile(newPath)
                newPath = newPath[len(pObj.GetWorkspace()) + 1:]
                section.AddDescription('-  \link %s \endlink 
 ' %  newPath)
            else:
                self.ProcessSourceFileForInclude(sPath, pObj, configFile, infObj)
                sPath = sPath[len(pObj.GetWorkspace()) + 1:]
                section.AddDescription('- \link %s \endlink 
 ' % sPath)
        section.AddDescription('
\n')
        modPage.AddSection(section)
        #sects = infObj.GetSectionByString('depex')
        data  = []
        #for sect in sects:
        for obj in infObj.GetSectionObjectsByName('depex'):
            data.append(str(obj))
        if len(data) != 0:
            s = doxygen.Section('DependentSection', 'Module Dependencies')
            s.AddDescription('
'.join(data))
            modPage.AddSection(s)
        return modPage
    def TranslateUniFile(self, path):
        newpath = path + '.dox'
        #import core.textfile as textfile
        #file = textfile.TextFile(path)
        try:
            file = open(path, 'r')
        except (IOError, OSError) as msg:
            return None
        t = file.read()
        file.close()
        output = '/** @file \n'
        #output = ''
        arr = t.split('\r\n')
        for line in arr:
            if line.find('@file') != -1:
                continue
            if line.find('*/') != -1:
                continue
            line = line.strip()
            if line.strip().startswith('/'):
                arr = line.split(' ')
                if len(arr) > 1:
                    line = ' '.join(arr[1:])
                else:
                    continue
            output += '%s
\n' % line
        output += '**/'
        if os.path.exists(newpath):
            os.remove(newpath)
        file = open(newpath, "w")
        file.write(output)
        file.close()
        return newpath
    def SearchPcdPackage(self, pcdname, workspace, decObjs):
        for decObj in  decObjs:
            for pcd in decObj.GetSectionObjectsByName('pcd'):
                if pcdname == pcd.GetPcdName():
                    return decObj.GetBaseName()
        return None
    def SearchProtocolPackage(self, protname, workspace, decObjs):
        for decObj in  decObjs:
            for proto in decObj.GetSectionObjectsByName('protocol'):
                if protname == proto.GetName():
                    return decObj.GetBaseName()
        return None
    def SearchPpiPackage(self, ppiname, workspace, decObjs):
        for decObj in  decObjs:
            for ppi in decObj.GetSectionObjectsByName('ppi'):
                if ppiname == ppi.GetName():
                    return decObj.GetBaseName()
        return None
    def SearchGuidPackage(self, guidname, workspace, decObjs):
        for decObj in  decObjs:
            for guid in decObj.GetSectionObjectsByName('guid'):
                if guidname == guid.GetName():
                    return decObj.GetBaseName()
        return None
    def SearchLibraryClassHeaderFile(self, className, workspace, decObjs):
        for decObj in  decObjs:
            for cls in decObj.GetSectionObjectsByName('libraryclasses'):
                if cls.GetClassName().strip() == className:
                    path = cls.GetHeaderFile().strip()
                    path = os.path.join(decObj.GetPackageRootPath(), path)
                    path = path[len(workspace) + 1:]
                    return decObj.GetBaseName(), path.replace('\\', '/')
        return None
    def _ConvertPathToDoxygen(self, path, pObj):
        pRootPath = pObj.GetWorkspace()
        path = path[len(pRootPath) + 1:]
        return path.replace('\\', '/')
def IsCHeaderFile(path):
    return CheckPathPostfix(path, 'h')
def CheckPathPostfix(path, str):
    index = path.rfind('.')
    if index == -1:
        return False
    if path[index + 1:].lower() == str.lower():
        return True
    return False