1*67e74705SXin Li# -*- coding: utf-8 -*- 2*67e74705SXin Li# The LLVM Compiler Infrastructure 3*67e74705SXin Li# 4*67e74705SXin Li# This file is distributed under the University of Illinois Open Source 5*67e74705SXin Li# License. See LICENSE.TXT for details. 6*67e74705SXin Li""" This module is responsible to generate 'index.html' for the report. 7*67e74705SXin Li 8*67e74705SXin LiThe input for this step is the output directory, where individual reports 9*67e74705SXin Licould be found. It parses those reports and generates 'index.html'. """ 10*67e74705SXin Li 11*67e74705SXin Liimport re 12*67e74705SXin Liimport os 13*67e74705SXin Liimport os.path 14*67e74705SXin Liimport sys 15*67e74705SXin Liimport shutil 16*67e74705SXin Liimport time 17*67e74705SXin Liimport tempfile 18*67e74705SXin Liimport itertools 19*67e74705SXin Liimport plistlib 20*67e74705SXin Liimport glob 21*67e74705SXin Liimport json 22*67e74705SXin Liimport logging 23*67e74705SXin Liimport contextlib 24*67e74705SXin Lifrom libscanbuild import duplicate_check 25*67e74705SXin Lifrom libscanbuild.clang import get_version 26*67e74705SXin Li 27*67e74705SXin Li__all__ = ['report_directory', 'document'] 28*67e74705SXin Li 29*67e74705SXin Li 30*67e74705SXin Li@contextlib.contextmanager 31*67e74705SXin Lidef report_directory(hint, keep): 32*67e74705SXin Li """ Responsible for the report directory. 33*67e74705SXin Li 34*67e74705SXin Li hint -- could specify the parent directory of the output directory. 35*67e74705SXin Li keep -- a boolean value to keep or delete the empty report directory. """ 36*67e74705SXin Li 37*67e74705SXin Li stamp = time.strftime('scan-build-%Y-%m-%d-%H%M%S-', time.localtime()) 38*67e74705SXin Li 39*67e74705SXin Li parentdir = os.path.abspath(hint) 40*67e74705SXin Li if not os.path.exists(parentdir): 41*67e74705SXin Li os.makedirs(parentdir) 42*67e74705SXin Li 43*67e74705SXin Li name = tempfile.mkdtemp(prefix=stamp, dir=parentdir) 44*67e74705SXin Li 45*67e74705SXin Li logging.info('Report directory created: %s', name) 46*67e74705SXin Li 47*67e74705SXin Li try: 48*67e74705SXin Li yield name 49*67e74705SXin Li finally: 50*67e74705SXin Li if os.listdir(name): 51*67e74705SXin Li msg = "Run 'scan-view %s' to examine bug reports." 52*67e74705SXin Li keep = True 53*67e74705SXin Li else: 54*67e74705SXin Li if keep: 55*67e74705SXin Li msg = "Report directory '%s' contans no report, but kept." 56*67e74705SXin Li else: 57*67e74705SXin Li msg = "Removing directory '%s' because it contains no report." 58*67e74705SXin Li logging.warning(msg, name) 59*67e74705SXin Li 60*67e74705SXin Li if not keep: 61*67e74705SXin Li os.rmdir(name) 62*67e74705SXin Li 63*67e74705SXin Li 64*67e74705SXin Lidef document(args, output_dir, use_cdb): 65*67e74705SXin Li """ Generates cover report and returns the number of bugs/crashes. """ 66*67e74705SXin Li 67*67e74705SXin Li html_reports_available = args.output_format in {'html', 'plist-html'} 68*67e74705SXin Li 69*67e74705SXin Li logging.debug('count crashes and bugs') 70*67e74705SXin Li crash_count = sum(1 for _ in read_crashes(output_dir)) 71*67e74705SXin Li bug_counter = create_counters() 72*67e74705SXin Li for bug in read_bugs(output_dir, html_reports_available): 73*67e74705SXin Li bug_counter(bug) 74*67e74705SXin Li result = crash_count + bug_counter.total 75*67e74705SXin Li 76*67e74705SXin Li if html_reports_available and result: 77*67e74705SXin Li logging.debug('generate index.html file') 78*67e74705SXin Li # common prefix for source files to have sort filenames 79*67e74705SXin Li prefix = commonprefix_from(args.cdb) if use_cdb else os.getcwd() 80*67e74705SXin Li # assemble the cover from multiple fragments 81*67e74705SXin Li try: 82*67e74705SXin Li fragments = [] 83*67e74705SXin Li if bug_counter.total: 84*67e74705SXin Li fragments.append(bug_summary(output_dir, bug_counter)) 85*67e74705SXin Li fragments.append(bug_report(output_dir, prefix)) 86*67e74705SXin Li if crash_count: 87*67e74705SXin Li fragments.append(crash_report(output_dir, prefix)) 88*67e74705SXin Li assemble_cover(output_dir, prefix, args, fragments) 89*67e74705SXin Li # copy additinal files to the report 90*67e74705SXin Li copy_resource_files(output_dir) 91*67e74705SXin Li if use_cdb: 92*67e74705SXin Li shutil.copy(args.cdb, output_dir) 93*67e74705SXin Li finally: 94*67e74705SXin Li for fragment in fragments: 95*67e74705SXin Li os.remove(fragment) 96*67e74705SXin Li return result 97*67e74705SXin Li 98*67e74705SXin Li 99*67e74705SXin Lidef assemble_cover(output_dir, prefix, args, fragments): 100*67e74705SXin Li """ Put together the fragments into a final report. """ 101*67e74705SXin Li 102*67e74705SXin Li import getpass 103*67e74705SXin Li import socket 104*67e74705SXin Li import datetime 105*67e74705SXin Li 106*67e74705SXin Li if args.html_title is None: 107*67e74705SXin Li args.html_title = os.path.basename(prefix) + ' - analyzer results' 108*67e74705SXin Li 109*67e74705SXin Li with open(os.path.join(output_dir, 'index.html'), 'w') as handle: 110*67e74705SXin Li indent = 0 111*67e74705SXin Li handle.write(reindent(""" 112*67e74705SXin Li |<!DOCTYPE html> 113*67e74705SXin Li |<html> 114*67e74705SXin Li | <head> 115*67e74705SXin Li | <title>{html_title}</title> 116*67e74705SXin Li | <link type="text/css" rel="stylesheet" href="scanview.css"/> 117*67e74705SXin Li | <script type='text/javascript' src="sorttable.js"></script> 118*67e74705SXin Li | <script type='text/javascript' src='selectable.js'></script> 119*67e74705SXin Li | </head>""", indent).format(html_title=args.html_title)) 120*67e74705SXin Li handle.write(comment('SUMMARYENDHEAD')) 121*67e74705SXin Li handle.write(reindent(""" 122*67e74705SXin Li | <body> 123*67e74705SXin Li | <h1>{html_title}</h1> 124*67e74705SXin Li | <table> 125*67e74705SXin Li | <tr><th>User:</th><td>{user_name}@{host_name}</td></tr> 126*67e74705SXin Li | <tr><th>Working Directory:</th><td>{current_dir}</td></tr> 127*67e74705SXin Li | <tr><th>Command Line:</th><td>{cmd_args}</td></tr> 128*67e74705SXin Li | <tr><th>Clang Version:</th><td>{clang_version}</td></tr> 129*67e74705SXin Li | <tr><th>Date:</th><td>{date}</td></tr> 130*67e74705SXin Li | </table>""", indent).format(html_title=args.html_title, 131*67e74705SXin Li user_name=getpass.getuser(), 132*67e74705SXin Li host_name=socket.gethostname(), 133*67e74705SXin Li current_dir=prefix, 134*67e74705SXin Li cmd_args=' '.join(sys.argv), 135*67e74705SXin Li clang_version=get_version(args.clang), 136*67e74705SXin Li date=datetime.datetime.today( 137*67e74705SXin Li ).strftime('%c'))) 138*67e74705SXin Li for fragment in fragments: 139*67e74705SXin Li # copy the content of fragments 140*67e74705SXin Li with open(fragment, 'r') as input_handle: 141*67e74705SXin Li shutil.copyfileobj(input_handle, handle) 142*67e74705SXin Li handle.write(reindent(""" 143*67e74705SXin Li | </body> 144*67e74705SXin Li |</html>""", indent)) 145*67e74705SXin Li 146*67e74705SXin Li 147*67e74705SXin Lidef bug_summary(output_dir, bug_counter): 148*67e74705SXin Li """ Bug summary is a HTML table to give a better overview of the bugs. """ 149*67e74705SXin Li 150*67e74705SXin Li name = os.path.join(output_dir, 'summary.html.fragment') 151*67e74705SXin Li with open(name, 'w') as handle: 152*67e74705SXin Li indent = 4 153*67e74705SXin Li handle.write(reindent(""" 154*67e74705SXin Li |<h2>Bug Summary</h2> 155*67e74705SXin Li |<table> 156*67e74705SXin Li | <thead> 157*67e74705SXin Li | <tr> 158*67e74705SXin Li | <td>Bug Type</td> 159*67e74705SXin Li | <td>Quantity</td> 160*67e74705SXin Li | <td class="sorttable_nosort">Display?</td> 161*67e74705SXin Li | </tr> 162*67e74705SXin Li | </thead> 163*67e74705SXin Li | <tbody>""", indent)) 164*67e74705SXin Li handle.write(reindent(""" 165*67e74705SXin Li | <tr style="font-weight:bold"> 166*67e74705SXin Li | <td class="SUMM_DESC">All Bugs</td> 167*67e74705SXin Li | <td class="Q">{0}</td> 168*67e74705SXin Li | <td> 169*67e74705SXin Li | <center> 170*67e74705SXin Li | <input checked type="checkbox" id="AllBugsCheck" 171*67e74705SXin Li | onClick="CopyCheckedStateToCheckButtons(this);"/> 172*67e74705SXin Li | </center> 173*67e74705SXin Li | </td> 174*67e74705SXin Li | </tr>""", indent).format(bug_counter.total)) 175*67e74705SXin Li for category, types in bug_counter.categories.items(): 176*67e74705SXin Li handle.write(reindent(""" 177*67e74705SXin Li | <tr> 178*67e74705SXin Li | <th>{0}</th><th colspan=2></th> 179*67e74705SXin Li | </tr>""", indent).format(category)) 180*67e74705SXin Li for bug_type in types.values(): 181*67e74705SXin Li handle.write(reindent(""" 182*67e74705SXin Li | <tr> 183*67e74705SXin Li | <td class="SUMM_DESC">{bug_type}</td> 184*67e74705SXin Li | <td class="Q">{bug_count}</td> 185*67e74705SXin Li | <td> 186*67e74705SXin Li | <center> 187*67e74705SXin Li | <input checked type="checkbox" 188*67e74705SXin Li | onClick="ToggleDisplay(this,'{bug_type_class}');"/> 189*67e74705SXin Li | </center> 190*67e74705SXin Li | </td> 191*67e74705SXin Li | </tr>""", indent).format(**bug_type)) 192*67e74705SXin Li handle.write(reindent(""" 193*67e74705SXin Li | </tbody> 194*67e74705SXin Li |</table>""", indent)) 195*67e74705SXin Li handle.write(comment('SUMMARYBUGEND')) 196*67e74705SXin Li return name 197*67e74705SXin Li 198*67e74705SXin Li 199*67e74705SXin Lidef bug_report(output_dir, prefix): 200*67e74705SXin Li """ Creates a fragment from the analyzer reports. """ 201*67e74705SXin Li 202*67e74705SXin Li pretty = prettify_bug(prefix, output_dir) 203*67e74705SXin Li bugs = (pretty(bug) for bug in read_bugs(output_dir, True)) 204*67e74705SXin Li 205*67e74705SXin Li name = os.path.join(output_dir, 'bugs.html.fragment') 206*67e74705SXin Li with open(name, 'w') as handle: 207*67e74705SXin Li indent = 4 208*67e74705SXin Li handle.write(reindent(""" 209*67e74705SXin Li |<h2>Reports</h2> 210*67e74705SXin Li |<table class="sortable" style="table-layout:automatic"> 211*67e74705SXin Li | <thead> 212*67e74705SXin Li | <tr> 213*67e74705SXin Li | <td>Bug Group</td> 214*67e74705SXin Li | <td class="sorttable_sorted"> 215*67e74705SXin Li | Bug Type 216*67e74705SXin Li | <span id="sorttable_sortfwdind"> ▾</span> 217*67e74705SXin Li | </td> 218*67e74705SXin Li | <td>File</td> 219*67e74705SXin Li | <td>Function/Method</td> 220*67e74705SXin Li | <td class="Q">Line</td> 221*67e74705SXin Li | <td class="Q">Path Length</td> 222*67e74705SXin Li | <td class="sorttable_nosort"></td> 223*67e74705SXin Li | </tr> 224*67e74705SXin Li | </thead> 225*67e74705SXin Li | <tbody>""", indent)) 226*67e74705SXin Li handle.write(comment('REPORTBUGCOL')) 227*67e74705SXin Li for current in bugs: 228*67e74705SXin Li handle.write(reindent(""" 229*67e74705SXin Li | <tr class="{bug_type_class}"> 230*67e74705SXin Li | <td class="DESC">{bug_category}</td> 231*67e74705SXin Li | <td class="DESC">{bug_type}</td> 232*67e74705SXin Li | <td>{bug_file}</td> 233*67e74705SXin Li | <td class="DESC">{bug_function}</td> 234*67e74705SXin Li | <td class="Q">{bug_line}</td> 235*67e74705SXin Li | <td class="Q">{bug_path_length}</td> 236*67e74705SXin Li | <td><a href="{report_file}#EndPath">View Report</a></td> 237*67e74705SXin Li | </tr>""", indent).format(**current)) 238*67e74705SXin Li handle.write(comment('REPORTBUG', {'id': current['report_file']})) 239*67e74705SXin Li handle.write(reindent(""" 240*67e74705SXin Li | </tbody> 241*67e74705SXin Li |</table>""", indent)) 242*67e74705SXin Li handle.write(comment('REPORTBUGEND')) 243*67e74705SXin Li return name 244*67e74705SXin Li 245*67e74705SXin Li 246*67e74705SXin Lidef crash_report(output_dir, prefix): 247*67e74705SXin Li """ Creates a fragment from the compiler crashes. """ 248*67e74705SXin Li 249*67e74705SXin Li pretty = prettify_crash(prefix, output_dir) 250*67e74705SXin Li crashes = (pretty(crash) for crash in read_crashes(output_dir)) 251*67e74705SXin Li 252*67e74705SXin Li name = os.path.join(output_dir, 'crashes.html.fragment') 253*67e74705SXin Li with open(name, 'w') as handle: 254*67e74705SXin Li indent = 4 255*67e74705SXin Li handle.write(reindent(""" 256*67e74705SXin Li |<h2>Analyzer Failures</h2> 257*67e74705SXin Li |<p>The analyzer had problems processing the following files:</p> 258*67e74705SXin Li |<table> 259*67e74705SXin Li | <thead> 260*67e74705SXin Li | <tr> 261*67e74705SXin Li | <td>Problem</td> 262*67e74705SXin Li | <td>Source File</td> 263*67e74705SXin Li | <td>Preprocessed File</td> 264*67e74705SXin Li | <td>STDERR Output</td> 265*67e74705SXin Li | </tr> 266*67e74705SXin Li | </thead> 267*67e74705SXin Li | <tbody>""", indent)) 268*67e74705SXin Li for current in crashes: 269*67e74705SXin Li handle.write(reindent(""" 270*67e74705SXin Li | <tr> 271*67e74705SXin Li | <td>{problem}</td> 272*67e74705SXin Li | <td>{source}</td> 273*67e74705SXin Li | <td><a href="{file}">preprocessor output</a></td> 274*67e74705SXin Li | <td><a href="{stderr}">analyzer std err</a></td> 275*67e74705SXin Li | </tr>""", indent).format(**current)) 276*67e74705SXin Li handle.write(comment('REPORTPROBLEM', current)) 277*67e74705SXin Li handle.write(reindent(""" 278*67e74705SXin Li | </tbody> 279*67e74705SXin Li |</table>""", indent)) 280*67e74705SXin Li handle.write(comment('REPORTCRASHES')) 281*67e74705SXin Li return name 282*67e74705SXin Li 283*67e74705SXin Li 284*67e74705SXin Lidef read_crashes(output_dir): 285*67e74705SXin Li """ Generate a unique sequence of crashes from given output directory. """ 286*67e74705SXin Li 287*67e74705SXin Li return (parse_crash(filename) 288*67e74705SXin Li for filename in glob.iglob(os.path.join(output_dir, 'failures', 289*67e74705SXin Li '*.info.txt'))) 290*67e74705SXin Li 291*67e74705SXin Li 292*67e74705SXin Lidef read_bugs(output_dir, html): 293*67e74705SXin Li """ Generate a unique sequence of bugs from given output directory. 294*67e74705SXin Li 295*67e74705SXin Li Duplicates can be in a project if the same module was compiled multiple 296*67e74705SXin Li times with different compiler options. These would be better to show in 297*67e74705SXin Li the final report (cover) only once. """ 298*67e74705SXin Li 299*67e74705SXin Li parser = parse_bug_html if html else parse_bug_plist 300*67e74705SXin Li pattern = '*.html' if html else '*.plist' 301*67e74705SXin Li 302*67e74705SXin Li duplicate = duplicate_check( 303*67e74705SXin Li lambda bug: '{bug_line}.{bug_path_length}:{bug_file}'.format(**bug)) 304*67e74705SXin Li 305*67e74705SXin Li bugs = itertools.chain.from_iterable( 306*67e74705SXin Li # parser creates a bug generator not the bug itself 307*67e74705SXin Li parser(filename) 308*67e74705SXin Li for filename in glob.iglob(os.path.join(output_dir, pattern))) 309*67e74705SXin Li 310*67e74705SXin Li return (bug for bug in bugs if not duplicate(bug)) 311*67e74705SXin Li 312*67e74705SXin Li 313*67e74705SXin Lidef parse_bug_plist(filename): 314*67e74705SXin Li """ Returns the generator of bugs from a single .plist file. """ 315*67e74705SXin Li 316*67e74705SXin Li content = plistlib.readPlist(filename) 317*67e74705SXin Li files = content.get('files') 318*67e74705SXin Li for bug in content.get('diagnostics', []): 319*67e74705SXin Li if len(files) <= int(bug['location']['file']): 320*67e74705SXin Li logging.warning('Parsing bug from "%s" failed', filename) 321*67e74705SXin Li continue 322*67e74705SXin Li 323*67e74705SXin Li yield { 324*67e74705SXin Li 'result': filename, 325*67e74705SXin Li 'bug_type': bug['type'], 326*67e74705SXin Li 'bug_category': bug['category'], 327*67e74705SXin Li 'bug_line': int(bug['location']['line']), 328*67e74705SXin Li 'bug_path_length': int(bug['location']['col']), 329*67e74705SXin Li 'bug_file': files[int(bug['location']['file'])] 330*67e74705SXin Li } 331*67e74705SXin Li 332*67e74705SXin Li 333*67e74705SXin Lidef parse_bug_html(filename): 334*67e74705SXin Li """ Parse out the bug information from HTML output. """ 335*67e74705SXin Li 336*67e74705SXin Li patterns = [re.compile(r'<!-- BUGTYPE (?P<bug_type>.*) -->$'), 337*67e74705SXin Li re.compile(r'<!-- BUGFILE (?P<bug_file>.*) -->$'), 338*67e74705SXin Li re.compile(r'<!-- BUGPATHLENGTH (?P<bug_path_length>.*) -->$'), 339*67e74705SXin Li re.compile(r'<!-- BUGLINE (?P<bug_line>.*) -->$'), 340*67e74705SXin Li re.compile(r'<!-- BUGCATEGORY (?P<bug_category>.*) -->$'), 341*67e74705SXin Li re.compile(r'<!-- BUGDESC (?P<bug_description>.*) -->$'), 342*67e74705SXin Li re.compile(r'<!-- FUNCTIONNAME (?P<bug_function>.*) -->$')] 343*67e74705SXin Li endsign = re.compile(r'<!-- BUGMETAEND -->') 344*67e74705SXin Li 345*67e74705SXin Li bug = { 346*67e74705SXin Li 'report_file': filename, 347*67e74705SXin Li 'bug_function': 'n/a', # compatibility with < clang-3.5 348*67e74705SXin Li 'bug_category': 'Other', 349*67e74705SXin Li 'bug_line': 0, 350*67e74705SXin Li 'bug_path_length': 1 351*67e74705SXin Li } 352*67e74705SXin Li 353*67e74705SXin Li with open(filename) as handler: 354*67e74705SXin Li for line in handler.readlines(): 355*67e74705SXin Li # do not read the file further 356*67e74705SXin Li if endsign.match(line): 357*67e74705SXin Li break 358*67e74705SXin Li # search for the right lines 359*67e74705SXin Li for regex in patterns: 360*67e74705SXin Li match = regex.match(line.strip()) 361*67e74705SXin Li if match: 362*67e74705SXin Li bug.update(match.groupdict()) 363*67e74705SXin Li break 364*67e74705SXin Li 365*67e74705SXin Li encode_value(bug, 'bug_line', int) 366*67e74705SXin Li encode_value(bug, 'bug_path_length', int) 367*67e74705SXin Li 368*67e74705SXin Li yield bug 369*67e74705SXin Li 370*67e74705SXin Li 371*67e74705SXin Lidef parse_crash(filename): 372*67e74705SXin Li """ Parse out the crash information from the report file. """ 373*67e74705SXin Li 374*67e74705SXin Li match = re.match(r'(.*)\.info\.txt', filename) 375*67e74705SXin Li name = match.group(1) if match else None 376*67e74705SXin Li with open(filename) as handler: 377*67e74705SXin Li lines = handler.readlines() 378*67e74705SXin Li return { 379*67e74705SXin Li 'source': lines[0].rstrip(), 380*67e74705SXin Li 'problem': lines[1].rstrip(), 381*67e74705SXin Li 'file': name, 382*67e74705SXin Li 'info': name + '.info.txt', 383*67e74705SXin Li 'stderr': name + '.stderr.txt' 384*67e74705SXin Li } 385*67e74705SXin Li 386*67e74705SXin Li 387*67e74705SXin Lidef category_type_name(bug): 388*67e74705SXin Li """ Create a new bug attribute from bug by category and type. 389*67e74705SXin Li 390*67e74705SXin Li The result will be used as CSS class selector in the final report. """ 391*67e74705SXin Li 392*67e74705SXin Li def smash(key): 393*67e74705SXin Li """ Make value ready to be HTML attribute value. """ 394*67e74705SXin Li 395*67e74705SXin Li return bug.get(key, '').lower().replace(' ', '_').replace("'", '') 396*67e74705SXin Li 397*67e74705SXin Li return escape('bt_' + smash('bug_category') + '_' + smash('bug_type')) 398*67e74705SXin Li 399*67e74705SXin Li 400*67e74705SXin Lidef create_counters(): 401*67e74705SXin Li """ Create counters for bug statistics. 402*67e74705SXin Li 403*67e74705SXin Li Two entries are maintained: 'total' is an integer, represents the 404*67e74705SXin Li number of bugs. The 'categories' is a two level categorisation of bug 405*67e74705SXin Li counters. The first level is 'bug category' the second is 'bug type'. 406*67e74705SXin Li Each entry in this classification is a dictionary of 'count', 'type' 407*67e74705SXin Li and 'label'. """ 408*67e74705SXin Li 409*67e74705SXin Li def predicate(bug): 410*67e74705SXin Li bug_category = bug['bug_category'] 411*67e74705SXin Li bug_type = bug['bug_type'] 412*67e74705SXin Li current_category = predicate.categories.get(bug_category, dict()) 413*67e74705SXin Li current_type = current_category.get(bug_type, { 414*67e74705SXin Li 'bug_type': bug_type, 415*67e74705SXin Li 'bug_type_class': category_type_name(bug), 416*67e74705SXin Li 'bug_count': 0 417*67e74705SXin Li }) 418*67e74705SXin Li current_type.update({'bug_count': current_type['bug_count'] + 1}) 419*67e74705SXin Li current_category.update({bug_type: current_type}) 420*67e74705SXin Li predicate.categories.update({bug_category: current_category}) 421*67e74705SXin Li predicate.total += 1 422*67e74705SXin Li 423*67e74705SXin Li predicate.total = 0 424*67e74705SXin Li predicate.categories = dict() 425*67e74705SXin Li return predicate 426*67e74705SXin Li 427*67e74705SXin Li 428*67e74705SXin Lidef prettify_bug(prefix, output_dir): 429*67e74705SXin Li def predicate(bug): 430*67e74705SXin Li """ Make safe this values to embed into HTML. """ 431*67e74705SXin Li 432*67e74705SXin Li bug['bug_type_class'] = category_type_name(bug) 433*67e74705SXin Li 434*67e74705SXin Li encode_value(bug, 'bug_file', lambda x: escape(chop(prefix, x))) 435*67e74705SXin Li encode_value(bug, 'bug_category', escape) 436*67e74705SXin Li encode_value(bug, 'bug_type', escape) 437*67e74705SXin Li encode_value(bug, 'report_file', lambda x: escape(chop(output_dir, x))) 438*67e74705SXin Li return bug 439*67e74705SXin Li 440*67e74705SXin Li return predicate 441*67e74705SXin Li 442*67e74705SXin Li 443*67e74705SXin Lidef prettify_crash(prefix, output_dir): 444*67e74705SXin Li def predicate(crash): 445*67e74705SXin Li """ Make safe this values to embed into HTML. """ 446*67e74705SXin Li 447*67e74705SXin Li encode_value(crash, 'source', lambda x: escape(chop(prefix, x))) 448*67e74705SXin Li encode_value(crash, 'problem', escape) 449*67e74705SXin Li encode_value(crash, 'file', lambda x: escape(chop(output_dir, x))) 450*67e74705SXin Li encode_value(crash, 'info', lambda x: escape(chop(output_dir, x))) 451*67e74705SXin Li encode_value(crash, 'stderr', lambda x: escape(chop(output_dir, x))) 452*67e74705SXin Li return crash 453*67e74705SXin Li 454*67e74705SXin Li return predicate 455*67e74705SXin Li 456*67e74705SXin Li 457*67e74705SXin Lidef copy_resource_files(output_dir): 458*67e74705SXin Li """ Copy the javascript and css files to the report directory. """ 459*67e74705SXin Li 460*67e74705SXin Li this_dir = os.path.dirname(os.path.realpath(__file__)) 461*67e74705SXin Li for resource in os.listdir(os.path.join(this_dir, 'resources')): 462*67e74705SXin Li shutil.copy(os.path.join(this_dir, 'resources', resource), output_dir) 463*67e74705SXin Li 464*67e74705SXin Li 465*67e74705SXin Lidef encode_value(container, key, encode): 466*67e74705SXin Li """ Run 'encode' on 'container[key]' value and update it. """ 467*67e74705SXin Li 468*67e74705SXin Li if key in container: 469*67e74705SXin Li value = encode(container[key]) 470*67e74705SXin Li container.update({key: value}) 471*67e74705SXin Li 472*67e74705SXin Li 473*67e74705SXin Lidef chop(prefix, filename): 474*67e74705SXin Li """ Create 'filename' from '/prefix/filename' """ 475*67e74705SXin Li 476*67e74705SXin Li return filename if not len(prefix) else os.path.relpath(filename, prefix) 477*67e74705SXin Li 478*67e74705SXin Li 479*67e74705SXin Lidef escape(text): 480*67e74705SXin Li """ Paranoid HTML escape method. (Python version independent) """ 481*67e74705SXin Li 482*67e74705SXin Li escape_table = { 483*67e74705SXin Li '&': '&', 484*67e74705SXin Li '"': '"', 485*67e74705SXin Li "'": ''', 486*67e74705SXin Li '>': '>', 487*67e74705SXin Li '<': '<' 488*67e74705SXin Li } 489*67e74705SXin Li return ''.join(escape_table.get(c, c) for c in text) 490*67e74705SXin Li 491*67e74705SXin Li 492*67e74705SXin Lidef reindent(text, indent): 493*67e74705SXin Li """ Utility function to format html output and keep indentation. """ 494*67e74705SXin Li 495*67e74705SXin Li result = '' 496*67e74705SXin Li for line in text.splitlines(): 497*67e74705SXin Li if len(line.strip()): 498*67e74705SXin Li result += ' ' * indent + line.split('|')[1] + os.linesep 499*67e74705SXin Li return result 500*67e74705SXin Li 501*67e74705SXin Li 502*67e74705SXin Lidef comment(name, opts=dict()): 503*67e74705SXin Li """ Utility function to format meta information as comment. """ 504*67e74705SXin Li 505*67e74705SXin Li attributes = '' 506*67e74705SXin Li for key, value in opts.items(): 507*67e74705SXin Li attributes += ' {0}="{1}"'.format(key, value) 508*67e74705SXin Li 509*67e74705SXin Li return '<!-- {0}{1} -->{2}'.format(name, attributes, os.linesep) 510*67e74705SXin Li 511*67e74705SXin Li 512*67e74705SXin Lidef commonprefix_from(filename): 513*67e74705SXin Li """ Create file prefix from a compilation database entries. """ 514*67e74705SXin Li 515*67e74705SXin Li with open(filename, 'r') as handle: 516*67e74705SXin Li return commonprefix(item['file'] for item in json.load(handle)) 517*67e74705SXin Li 518*67e74705SXin Li 519*67e74705SXin Lidef commonprefix(files): 520*67e74705SXin Li """ Fixed version of os.path.commonprefix. Return the longest path prefix 521*67e74705SXin Li that is a prefix of all paths in filenames. """ 522*67e74705SXin Li 523*67e74705SXin Li result = None 524*67e74705SXin Li for current in files: 525*67e74705SXin Li if result is not None: 526*67e74705SXin Li result = os.path.commonprefix([result, current]) 527*67e74705SXin Li else: 528*67e74705SXin Li result = current 529*67e74705SXin Li 530*67e74705SXin Li if result is None: 531*67e74705SXin Li return '' 532*67e74705SXin Li elif not os.path.isdir(result): 533*67e74705SXin Li return os.path.dirname(result) 534*67e74705SXin Li else: 535*67e74705SXin Li return os.path.abspath(result) 536