22
33from __future__ import unicode_literals
44
5+ import datetime
56import io
6- import sys
7+ import locale
8+ import operator
79import optparse
810import os
9- import operator
11+ import sys
12+ import subprocess
1013
1114from collections import Counter
1215from pygments import highlight
@@ -287,6 +290,74 @@ def html_escape(text):
287290 return escape (text , html_escape_table )
288291
289292
293+ def git_blame (line , path , file , blame_options ):
294+ git_blame_dict = {}
295+ head , tail = os .path .split (file )
296+ if head != "" :
297+ path = head
298+
299+ try :
300+ os .chdir (path )
301+ except :
302+ return {}
303+
304+ try :
305+ result = subprocess .check_output ('git blame -L %d %s %s --porcelain -- %s' % (
306+ line , " -w" if "-w" in blame_options else "" , " -M" if "-M" in blame_options else "" , file ))
307+ result = result .decode (locale .getpreferredencoding ())
308+ except :
309+ return {}
310+
311+ if result .startswith ('fatal' ):
312+ return {}
313+
314+ disallowed_characters = '<>'
315+ for line in result .split ('\n ' )[1 :]:
316+ space_pos = line .find (' ' )
317+ if space_pos > 30 :
318+ break
319+ key = line [:space_pos ]
320+ val = line [space_pos + 1 :]
321+
322+ for character in disallowed_characters :
323+ val = val .replace (character , "" )
324+ git_blame_dict [key ] = val
325+
326+ datetime_object = datetime .date .fromtimestamp (float (git_blame_dict ['author-time' ]))
327+ year = datetime_object .strftime ("%Y" )
328+ month = datetime_object .strftime ("%m" )
329+ day = datetime_object .strftime ("%d" )
330+
331+ git_blame_dict ['author-time' ] = '%s/%s/%s' % (day , month , year )
332+
333+ return git_blame_dict
334+
335+
336+ def tr_str (td_th , line , id , cwe , severity , message , author , author_mail , date , add_author , tr_class = None , htmlfile = None , message_class = None ):
337+ ret = ''
338+ if htmlfile :
339+ ret += '<%s><a href="%s#line-%d">%d</a></%s>' % (td_th , htmlfile , line , line , td_th )
340+ for item in (id , cwe , severity ):
341+ ret += '<%s>%s</%s>' % (td_th , item , td_th )
342+ else :
343+ for item in (line , id , cwe , severity ):
344+ ret += '<%s>%s</%s>' % (td_th , item , td_th )
345+ if message_class :
346+ message_attribute = ' class="%s"' % message_class
347+ else :
348+ message_attribute = ''
349+ ret += '<%s%s>%s</%s>' % (td_th , message_attribute , html_escape (message ), td_th )
350+
351+ if add_author :
352+ for item in (author , author_mail , date ):
353+ ret += '<%s>%s</%s>' % (td_th , item , td_th )
354+ if tr_class :
355+ tr_attributes = ' class="%s"' % tr_class
356+ else :
357+ tr_attributes = ''
358+ return '<tr%s>%s</tr>' % (tr_attributes , ret )
359+
360+
290361class AnnotateCodeFormatter (HtmlFormatter ):
291362 errors = []
292363
@@ -405,8 +476,15 @@ if __name__ == '__main__':
405476 parser .add_option ('--source-dir' , dest = 'source_dir' ,
406477 help = 'Base directory where source code files can be '
407478 'found.' )
479+ parser .add_option ('--add-author-information' , dest = 'add_author_information' ,
480+ help = 'Initially set to false'
481+ 'Adds author, author-mail and time to htmlreport' )
408482 parser .add_option ('--source-encoding' , dest = 'source_encoding' ,
409483 help = 'Encoding of source code.' , default = 'utf-8' )
484+ parser .add_option ('--blame-options' , dest = 'blame_options' ,
485+ help = '[-w, -M] blame options which you can use to get author and author mail '
486+ '-w --> not including white spaces and returns original author of the line '
487+ '-M --> not including moving of lines and returns original author of the line' )
410488
411489 # Parse options and make sure that we have an output directory set.
412490 options , args = parser .parse_args ()
@@ -421,10 +499,19 @@ if __name__ == '__main__':
421499 parser .error ('No report directory set.' )
422500
423501 # Get the directory where source code files are located.
502+ cwd = os .getcwd ()
424503 source_dir = os .getcwd ()
425504 if options .source_dir :
426505 source_dir = options .source_dir
427506
507+ add_author_information = False
508+ if options .add_author_information :
509+ add_author_information = True
510+
511+ blame_options = ''
512+ if options .blame_options :
513+ blame_options = options .blame_options
514+ add_author_information = True
428515 # Parse the xml from all files defined in file argument
429516 # or from stdin. If no input is provided, stdin is used
430517 # Produce a simple list of errors.
@@ -589,7 +676,10 @@ if __name__ == '__main__':
589676 output_file .write (HTML_HEAD_END .replace ("content" , "content_index" , 1 ))
590677
591678 output_file .write ('\n <table>' )
592- output_file .write ('\n <tr><th>Line</th><th>Id</th><th>CWE</th><th>Severity</th><th>Message</th></tr>' )
679+ output_file .write (
680+ '\n %s' %
681+ tr_str ('th' , 'Line' , 'Id' , 'CWE' , 'Severity' , 'Message' , 'Author' , 'Author mail' , 'Date (DD/MM/YYYY)' , add_author = add_author_information ))
682+
593683 for filename , data in sorted (files .items ()):
594684 if filename in decode_errors : # don't print a link but a note
595685 output_file .write ("\n <tr><td colspan=\" 5\" >%s</td></tr>" % filename )
@@ -605,10 +695,14 @@ if __name__ == '__main__':
605695 (data ['htmlfile' ], filename ))
606696
607697 for error in sorted (data ['errors' ], key = lambda k : k ['line' ]):
608- error_class = ''
698+ if add_author_information :
699+ git_blame_dict = git_blame (error ['line' ], source_dir , error ['file' ], blame_options )
700+ else :
701+ git_blame_dict = {}
702+ message_class = None
609703 try :
610704 if error ['inconclusive' ] == 'true' :
611- error_class = 'class=" inconclusive" '
705+ message_class = 'inconclusive'
612706 error ['severity' ] += ", inconcl."
613707 except KeyError :
614708 pass
@@ -620,23 +714,21 @@ if __name__ == '__main__':
620714 cwe_url = ""
621715
622716 if error ['severity' ] == 'error' :
623- error_class = 'class="error"'
624- if error ['id' ] == 'missingInclude' :
625- output_file .write (
626- '\n <tr class="%s"><td></td><td>%s</td><td></td><td>%s</td><td>%s</td></tr>' %
627- (error ['id' ], error ['id' ], error ['severity' ], html_escape (error ['msg' ])))
628- elif (error ['id' ] == 'unmatchedSuppression' ) and filename .endswith ('*' ):
629- output_file .write (
630- '\n <tr class="%s"><td></td><td>%s</td><td></td><td>%s</td><td %s>%s</td></tr>' %
631- (error ['id' ], error ['id' ], error ['severity' ], error_class ,
632- html_escape (error ['msg' ])))
633- else :
634- output_file .write (
635- '\n <tr class="%s"><td><a href="%s#line-%d">%d</a></td><td>%s</td><td>%s</td><td>%s</td><td %s>%s</td></tr>' %
636- (error ['id' ], data ['htmlfile' ], error ['line' ], error ['line' ],
637- error ['id' ], cwe_url , error ['severity' ], error_class ,
638- html_escape (error ['msg' ])))
717+ message_class = 'error'
639718
719+ is_file = filename != '' and not filename .endswith ('*' )
720+ line = error ["line" ] if is_file else ""
721+ htmlfile = data .get ('htmlfile' ) if is_file else None
722+
723+ output_file .write (
724+ '\n %s' %
725+ tr_str ('td' , line , error ["id" ], cwe_url , error ["severity" ], error ["msg" ],
726+ git_blame_dict .get ('author' , 'Unknown' ), git_blame_dict .get ('author-mail' , '---' ),
727+ git_blame_dict .get ('author-time' , '---' ),
728+ tr_class = error ["id" ],
729+ message_class = message_class ,
730+ add_author = add_author_information ,
731+ htmlfile = htmlfile ))
640732 output_file .write ('\n </table>' )
641733 output_file .write (HTML_FOOTER % contentHandler .versionCppcheck )
642734
@@ -645,8 +737,8 @@ if __name__ == '__main__':
645737 sys .stderr .write ("\n Consider changing source-encoding (for example: \" htmlreport ... --source-encoding=\" iso8859-1\" \" \n " )
646738
647739 print ('Creating style.css file' )
648- with io . open ( os .path . join ( options . report_dir , ' style.css' ),
649- 'w' ) as css_file :
740+ os .chdir ( cwd ) # going back to the cwd to find style.css
741+ with io . open ( os . path . join ( options . report_dir , 'style.css' ), 'w' ) as css_file :
650742 css_file .write (STYLE_FILE )
651743
652744 print ("Creating stats.html (statistics)\n " )
0 commit comments