#!/usr/bin/python

"""
Extract test cases information from Cppcheck test
file
"""

import os
import sys
import re

class Extract:
    """
    Read Cppcheck test file and create data
    representation
    """

    # array that stores all the test cases
    nodes = []

    def parseFile(self,filename):
        """
        parse test file and add info to the nodes
        variable
        """

        name = '[0-9a-zA-Z_]+'
        string =  '\\"(.+)\\"'

        testclass = None
        functionName = None

        fin = open(filename, 'r')
        for line in fin:
            # testclass starts
            res = re.match('class ('+name+')', line)
            if res != None:
                testclass = res.group(1)

            # end of testclass 
            if re.match('};', line) != None:
                testclass = None

            # function start
            res = re.match('\\s+void ('+name+')\\(\\)', line)
            if res != None:
                functionName = res.group(1)

            elif re.match('\\s+}', line) != None:
                functionName = None

            if functionName == None:
                continue

            # check
            res = re.match('\s+check.*\('+string, line)
            if res != None:
                code = res.group(1)

            # code..
            res = re.match('\\s+'+string, line)
            if res != None:
                code = code + res.group(1)

            # assert
            res = re.match('\\s+ASSERT_EQUALS\\(\\"([^"]*)\\",', line)
            if res != None and len(code) > 10:
                node = { 'testclass':testclass,
                         'functionName':functionName,
                         'code':code,
                         'expected':res.group(1) }
                self.nodes.append(node)
                code = ''

        # close test file
        fin.close()

def strtoxml(s):
    """Convert string to xml/html format"""
    return s.replace('&','&amp;').replace('"', '&quot;').replace('<','&lt;').replace('>','&gt;')

def trimname(name):
    """Trim test name. Trailing underscore and digits are removed"""
    while name[-1].isdigit():
        name = name[:-1]
    if name[-1] == '_':
        name = name[:-1]
    return name


def writeHtmlFile(nodes, functionName, filename, errorsOnly):
    """Write html file for a function name"""
    fout = open(filename, 'w')
    fout.write('<html>\n')
    fout.write('<head>\n')
    fout.write('  <style type="text/css">\n')
    fout.write('  body { font-size: 0.8em }\n')
    fout.write('  th { background-color: #A3C159; text-transform: uppercase }\n')
    fout.write('  td { background-color: white; vertical-align: text-top }\n')
    fout.write('  pre { background-color: #EEEEEE }\n')
    fout.write('  </style>\n')
    fout.write('</head>\n')
    fout.write('<body>\n')

    fout.write('<a href="index.htm">Home</a> -- ')
    if errorsOnly:
        fout.write('<a href="all-'+functionName+'.htm">All test cases</a>')
    else:
        fout.write('<a href="errors-'+functionName+'.htm">Error test cases</a>')
    fout.write('<br><br>')

    testclass = None
    num = 0
    for node in nodes:
        if errorsOnly and node['expected']=='':
            continue
        if trimname(node['functionName']) == functionName:
            num = num + 1

            if not testclass:
                testclass = node['testclass']
                fout.write('<h1>' + node['testclass'] + '::' + functionName + '</h1>')
                fout.write('<table border="0" cellspacing="0">\n')
                fout.write('  <tr><th>Nr</th><th>Code</th><th>Expected</th></tr>\n')

            fout.write('  <tr><td>' + str(num) + '</td>')
            fout.write('<td><pre>' + strtoxml(node['code']).replace('\\n', '\n') + '</pre></td>')
            fout.write('<td>' + strtoxml(node['expected']).replace('\\n', '<br>') + '</td>')
            fout.write('</tr>\n')

    if testclass != None:
        fout.write('</table>\n');
    fout.write('</body></html>\n')
    fout.close()


if len(sys.argv) <= 1 or '--help' in sys.argv:
    print 'Extract test cases from test file'
    print 'Syntax: extracttests.py [--html=folder] [--xml] [--code=folder] path/testfile.cpp'
    sys.exit(0)

# parse command line
xml = False
filename = None
htmldir = None
codedir = None
for arg in sys.argv[1:]:
    if arg == '--xml':
        xml = True
    elif arg.startswith('--html='):
        htmldir = arg[7:]
    elif arg.startswith('--code='):
        codedir = arg[7:]
    elif arg.endswith('.cpp'):
        filename = arg
    else:
        print 'Invalid option: ' + arg
        sys.exit(1)


# extract test cases
if filename != None:
    # parse test file
    e = Extract()
    e.parseFile(filename)

    # generate output
    if xml:
        print '<?xml version="1.0"?>'
        print '<tree>'
        count = 0
        for node in e.nodes:
            s  = '  <node'
            s += ' function="' + node['functionName'] + '"'
            s += ' code="' + strtoxml(node['code']) + '"'
            s += ' expected="' + strtoxml(node['expected']) + '"'
            s += '/>'
            print s
        print '</tree>'
    elif htmldir != None:
        if not htmldir.endswith('/'):
            htmldir += '/'
        if not os.path.exists(htmldir):
            os.mkdir(htmldir)
        findex = open(htmldir + 'index.htm', 'w')
        findex.write('<html>\n')
        findex.write('<head>\n')
        findex.write('  <style type="text/css">\n')
        findex.write('  table { font-size: 0.8em }\n')
        findex.write('  th { background-color: #A3C159; text-transform: uppercase }\n')
        findex.write('  td { background-color: #F0FFE0; vertical-align: text-top }\n')
        findex.write('  A:link { text-decoration: none }\n')
        findex.write('  A:visited { text-decoration: none }\n')
        findex.write('  A:active { text-decoration: none }\n')
        findex.write('  A:hover { text-decoration: underline; color: blue }\n')
        findex.write('  </style>\n')
        findex.write('</head>\n')
        findex.write('<body>\n')
        findex.write('<h1>' + filename + '</h1>\n')

        functionNames = []
        for node in e.nodes:
            functionname = trimname(node['functionName'])
            if not functionname in functionNames:
                functionNames.append(functionname)
        functionNames.sort()

        findex.write('<table border="0" cellspacing="0">\n')
        findex.write('  <tr><th>Name</th><th>Errors</th><th>All</th></tr>\n')
        for functionname in functionNames:
            findex.write('  <tr><td>'+functionname+'</td>')
            numall = 0
            numerr = 0
            for node in e.nodes:
                if trimname(node['functionName']) == functionname:
                    numall = numall + 1
                    if node['expected'] != '':
                        numerr = numerr + 1
            if numerr == 0:
                findex.write('<td><div align="right">0</div></td>')
            else:
                findex.write('<td><a href="errors-'+functionname+'.htm"><div align="right">' + str(numerr) + '</div></a></td>')
            findex.write('<td><a href="all-'+functionname+'.htm"><div align="right">' + str(numall) + '</div></a></td>')
            findex.write('</tr>\n')

        findex.write('</table>\n')

        findex.write('</body></html>')
        findex.close()

        # create files for each functionName
        for functionName in functionNames:
            writeHtmlFile(e.nodes, 
                          functionName, 
                          htmldir + 'errors-' + functionName + '.htm',
                          True)
            writeHtmlFile(e.nodes,
                          functionName,
                          htmldir + 'all-' + functionName + '.htm',
                          False)

    elif codedir:
        testnum = 0

        if not codedir.endswith('/'):
            codedir = codedir + '/'

        if not os.path.exists(codedir):
            os.mkdir(codedir)

        errors = open(codedir+'errors.txt', 'w')

        for node in e.nodes:
            testnum = testnum + 1

            functionName = node['functionName']
            code = node['code']
            code = code.replace('\\n', '\n')
            code = code.replace('\\"', '"')
            expected = node['expected']

            filename = '0000' + str(testnum) + '-'
            filename = filename[-4:]
            filename += functionName + '.cpp'

            # source code
            fout = open(codedir+filename,'w')
            fout.write(code)
            fout.close()

            # write 'expected' to errors.txt
            if expected != '':
                expected = expected.replace('\\n', '\n')
                expected = expected.replace('\\"', '"')
                expected = re.sub('\\[test.cp?p?:', '['+filename+':', expected)
                errors.write(expected)
        errors.close()
    else:
        for node in e.nodes:
            print node['functionName']