Htmlreport changes (#3690)
This commit is contained in:
parent
7aa0ec3e95
commit
423c4f8207
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import datetime
|
from datetime import date
|
||||||
import io
|
import io
|
||||||
import locale
|
import locale
|
||||||
import operator
|
import operator
|
||||||
|
@ -74,7 +74,7 @@ h1 {
|
||||||
grid-area: menu;
|
grid-area: menu;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
padding: 0 15px 15px 15px;
|
padding: 0 23px 15px 15px;
|
||||||
border-right: thin solid #aaa;
|
border-right: thin solid #aaa;
|
||||||
min-width: 200px;
|
min-width: 200px;
|
||||||
}
|
}
|
||||||
|
@ -92,6 +92,45 @@ h1 {
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
label {
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
label.checkBtn.disabled {
|
||||||
|
color: #606060;
|
||||||
|
background: #e0e0e0;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
|
label.checkBtn, input[type="text"] {
|
||||||
|
border: 1px solid grey;
|
||||||
|
border-radius: 4px;
|
||||||
|
box-shadow: 1px 1px inset;
|
||||||
|
padding: 1px 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
label.checkBtn {
|
||||||
|
white-space: nowrap;
|
||||||
|
background: #ccddff;
|
||||||
|
}
|
||||||
|
|
||||||
|
label.unchecked {
|
||||||
|
background: #eff8ff;
|
||||||
|
box-shadow: 1px 1px 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
label.checkBtn:hover, label.unchecked:hover{
|
||||||
|
box-shadow: 0 0 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
label.disabled:hover {
|
||||||
|
box-shadow: 1px 1px inset;
|
||||||
|
}
|
||||||
|
|
||||||
|
label.checkBtn > input {
|
||||||
|
display:none;
|
||||||
|
}
|
||||||
|
|
||||||
.summaryTable {
|
.summaryTable {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
@ -160,7 +199,7 @@ table.summaryTable td { padding: 0 5px 0 5px; }
|
||||||
padding-right: 6px;
|
padding-right: 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.id-filtered, .severity-filtered, .file-filtered, .tool-filtered {
|
.id-filtered, .severity-filtered, .file-filtered, .tool-filtered, .text-filtered {
|
||||||
visibility: collapse;
|
visibility: collapse;
|
||||||
}
|
}
|
||||||
"""
|
"""
|
||||||
|
@ -228,6 +267,7 @@ HTML_HEAD = """
|
||||||
}
|
}
|
||||||
|
|
||||||
function toggleSeverity(cb) {
|
function toggleSeverity(cb) {
|
||||||
|
cb.parentElement.classList.toggle("unchecked", !cb.checked);
|
||||||
var elements = document.querySelectorAll(".sev_" + cb.id);
|
var elements = document.querySelectorAll(".sev_" + cb.id);
|
||||||
|
|
||||||
for (var i = 0, len = elements.length; i < len; i++) {
|
for (var i = 0, len = elements.length; i < len; i++) {
|
||||||
|
@ -238,6 +278,8 @@ HTML_HEAD = """
|
||||||
}
|
}
|
||||||
|
|
||||||
function toggleTool(cb) {
|
function toggleTool(cb) {
|
||||||
|
cb.parentElement.classList.toggle("unchecked", !cb.checked);
|
||||||
|
|
||||||
var elements;
|
var elements;
|
||||||
if (cb.id == "clang-tidy")
|
if (cb.id == "clang-tidy")
|
||||||
elements = document.querySelectorAll("[class^=clang-tidy-]");
|
elements = document.querySelectorAll("[class^=clang-tidy-]");
|
||||||
|
@ -264,13 +306,40 @@ HTML_HEAD = """
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function filterFile(filter) {
|
||||||
|
var elements = document.querySelectorAll(".fileEntry");
|
||||||
|
|
||||||
|
for (var i = 0, len = elements.length; i < len; i++) {
|
||||||
|
var visible = elements[i].querySelector("tr").querySelector("td").textContent.toLowerCase().includes(filter.toLowerCase());
|
||||||
|
elements[i].classList.toggle("text-filtered", !visible);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function filterText(text) {
|
||||||
|
filter = text.toLowerCase();
|
||||||
|
var elements = document.querySelectorAll(".issue");
|
||||||
|
|
||||||
|
for (var i = 0, len = elements.length; i < len; i++) {
|
||||||
|
var visible = false;
|
||||||
|
var fields = elements[i].querySelectorAll("td");
|
||||||
|
for (var n = 0, num = fields.length; n < num; n++) {
|
||||||
|
if (fields[n].textContent.toLowerCase().includes(filter)) {
|
||||||
|
visible = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
elements[i].classList.toggle("text-filtered", !visible);
|
||||||
|
}
|
||||||
|
|
||||||
|
updateFileRows();
|
||||||
|
}
|
||||||
|
|
||||||
function updateFileRows(element) {
|
function updateFileRows(element) {
|
||||||
var elements = document.querySelectorAll(".fileEntry");
|
var elements = document.querySelectorAll(".fileEntry");
|
||||||
|
|
||||||
for (var i = 0, len = elements.length; i < len; i++) {
|
for (var i = 0, len = elements.length; i < len; i++) {
|
||||||
var issue_count = elements[i].querySelectorAll(".issue").length;
|
var visible = elements[i].querySelector(".issue:not(.id-filtered):not(.severity-filtered):not(.tool-filtered):not(.text-filtered)");
|
||||||
var invisible_count = elements[i].querySelectorAll(".id-filtered, .severity-filtered, .tool-filtered").length;
|
elements[i].classList.toggle("file-filtered", !visible);
|
||||||
elements[i].classList.toggle("file-filtered", (invisible_count == issue_count));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -283,21 +352,6 @@ HTML_HEAD = """
|
||||||
<h1>Cppcheck report - %s%s</h1>
|
<h1>Cppcheck report - %s%s</h1>
|
||||||
"""
|
"""
|
||||||
|
|
||||||
HTML_SEVERITY_FILTER = """
|
|
||||||
<div id="filters">
|
|
||||||
<span class="severityHeader">Severity:</span>
|
|
||||||
<label><input type="checkbox" class="severityToggle" onclick="toggleSeverity(this)" checked id=\"error\">Error</label>
|
|
||||||
<label><input type="checkbox" class="severityToggle" onclick="toggleSeverity(this)" checked id=\"warning\">Warning</label>
|
|
||||||
<label><input type="checkbox" class="severityToggle" onClick="toggleSeverity(this)" checked id=\"portability\">Portability</label>
|
|
||||||
<label><input type="checkbox" class="severityToggle" onclick="toggleSeverity(this)" checked id=\"performance\">Performance</label>
|
|
||||||
<label><input type="checkbox" class="severityToggle" onclick="toggleSeverity(this)" checked id=\"style\">Style</label>
|
|
||||||
<label><input type="checkbox" class="severityToggle" onclick="toggleSeverity(this)" checked id=\"information\">Information</label>
|
|
||||||
| <span class="severityHeader">Tool:</span>
|
|
||||||
<label><input type="checkbox" class="severityToggle" onclick="toggleTool(this)" checked id=\"cppcheck\">Cppcheck</label>
|
|
||||||
<label><input type="checkbox" class="severityToggle" onclick="toggleTool(this)" checked id=\"clang-tidy\">clang-tidy</label>
|
|
||||||
</div>
|
|
||||||
"""
|
|
||||||
|
|
||||||
HTML_HEAD_END = """
|
HTML_HEAD_END = """
|
||||||
</div>
|
</div>
|
||||||
"""
|
"""
|
||||||
|
@ -341,54 +395,77 @@ html_unescape_table = {v: k for k, v in html_escape_table.items()}
|
||||||
def html_escape(text):
|
def html_escape(text):
|
||||||
return escape(text, html_escape_table)
|
return escape(text, html_escape_table)
|
||||||
|
|
||||||
|
def filter_button(enabled_filters, id, function):
|
||||||
|
enabled = enabled_filters.get(id, False)
|
||||||
|
return '\n <label class="checkBtn%s"><input type="checkbox" onclick="%s(this)" id="%s"%s/>%s</label>'\
|
||||||
|
% (' disabled' if not enabled else '', function, id, 'checked' if enabled else 'disabled', id)
|
||||||
|
|
||||||
def git_blame(line, path, file, blame_options):
|
def filter_bar(enabled):
|
||||||
git_blame_dict = {}
|
return ''.join([
|
||||||
|
' <div id="filters">\n'
|
||||||
|
,''.join([filter_button(enabled, severity, 'toggleSeverity') for severity in ['error', 'warning', 'portability', 'performance', 'style', 'information']])
|
||||||
|
,'\n | '
|
||||||
|
,''.join([filter_button(enabled, tool, 'toggleTool') for tool in ['cppcheck', 'clang-tidy']])
|
||||||
|
,'\n | '
|
||||||
|
,'\n <label class="severityHeader">File: <input type="text" oninput="filterFile(this.value)"/></label>'
|
||||||
|
,'\n <label class="severityHeader">Filter: <input type="text" oninput="filterText(this.value)"/></label>'
|
||||||
|
,'\n </div>\n'])
|
||||||
|
|
||||||
|
def git_blame(errors, path, file, blame_options):
|
||||||
|
last_line= errors[-1]['line']
|
||||||
|
if last_line == 0:
|
||||||
|
return {}
|
||||||
|
|
||||||
|
first_line = next((error for error in errors if error['line'] > 0))['line']
|
||||||
|
|
||||||
full_path = os.path.join(path, file)
|
full_path = os.path.join(path, file)
|
||||||
path, file = os.path.split(full_path)
|
path, filename = os.path.split(full_path)
|
||||||
|
|
||||||
if path:
|
if path:
|
||||||
os.chdir(path)
|
os.chdir(path)
|
||||||
|
|
||||||
cmd_args = ['git', 'blame', '-L %d' % line]
|
cmd_args = ['git', 'blame', '-L %d,%d' % (first_line, last_line)]
|
||||||
if '-w' in blame_options:
|
if '-w' in blame_options:
|
||||||
cmd_args.append('-w')
|
cmd_args.append('-w')
|
||||||
if '-M' in blame_options:
|
if '-M' in blame_options:
|
||||||
cmd_args.append('-M')
|
cmd_args.append('-M')
|
||||||
cmd_args = cmd_args + ['--porcelain', '--', file]
|
cmd_args = cmd_args + ['--porcelain', '--incremental', '--', filename]
|
||||||
|
|
||||||
try:
|
try:
|
||||||
result = subprocess.check_output(cmd_args)
|
result = subprocess.check_output(cmd_args)
|
||||||
result = result.decode(locale.getpreferredencoding())
|
result = result.decode(locale.getpreferredencoding())
|
||||||
except:
|
except:
|
||||||
return {}
|
return []
|
||||||
finally:
|
finally:
|
||||||
os.chdir(cwd)
|
os.chdir(cwd)
|
||||||
|
|
||||||
if result.startswith('fatal'):
|
if result.startswith('fatal'):
|
||||||
return {}
|
return []
|
||||||
|
|
||||||
disallowed_characters = '<>'
|
blame_data = []
|
||||||
for line in result.split('\n')[1:]:
|
line_from = 0
|
||||||
space_pos = line.find(' ')
|
line_to = 0
|
||||||
if space_pos > 30:
|
blame_info = dict()
|
||||||
break
|
|
||||||
key = line[:space_pos]
|
|
||||||
val = line[space_pos + 1:]
|
|
||||||
|
|
||||||
for character in disallowed_characters:
|
for line_str in result.splitlines():
|
||||||
val = val.replace(character, "")
|
field, _, value = line_str.partition(' ')
|
||||||
git_blame_dict[key] = val
|
if len(field) == 40:
|
||||||
|
line_nr, count = value.split(' ')[1:]
|
||||||
|
line_from = int(line_nr)
|
||||||
|
line_to = line_from + int(count)
|
||||||
|
elif field.startswith('author'):
|
||||||
|
blame_info[field] = value
|
||||||
|
if field == 'author-time':
|
||||||
|
author_date = date.fromtimestamp(int(blame_info['author-time']))
|
||||||
|
blame_info['author-time'] = author_date.strftime("%d/%m/%Y")
|
||||||
|
elif field == 'filename':
|
||||||
|
blame_data.append([line_from, line_to, blame_info.copy()])
|
||||||
|
|
||||||
datetime_object = datetime.date.fromtimestamp(float(git_blame_dict['author-time']))
|
return blame_data
|
||||||
year = datetime_object.strftime("%Y")
|
|
||||||
month = datetime_object.strftime("%m")
|
|
||||||
day = datetime_object.strftime("%d")
|
|
||||||
|
|
||||||
git_blame_dict['author-time'] = '%s/%s/%s' % (day, month, year)
|
|
||||||
|
|
||||||
return git_blame_dict
|
def blame_lookup(blame_data, line):
|
||||||
|
return next((data for start, end, data in blame_data if line >= start and line < end), {})
|
||||||
|
|
||||||
|
|
||||||
def tr_str(td_th, line, id, cwe, severity, message, author, author_mail, date, add_author, tr_class=None, htmlfile=None, message_class=None):
|
def tr_str(td_th, line, id, cwe, severity, message, author, author_mail, date, add_author, tr_class=None, htmlfile=None, message_class=None):
|
||||||
|
@ -408,9 +485,9 @@ def tr_str(td_th, line, id, cwe, severity, message, author, author_mail, date, a
|
||||||
|
|
||||||
for field in add_author:
|
for field in add_author:
|
||||||
if field == 'name':
|
if field == 'name':
|
||||||
ret += '<%s>%s</%s>' % (td_th, author, td_th)
|
ret += '<%s>%s</%s>' % (td_th, html_escape(author), td_th)
|
||||||
elif field == 'email':
|
elif field == 'email':
|
||||||
ret += '<%s>%s</%s>' % (td_th, author_mail, td_th)
|
ret += '<%s>%s</%s>' % (td_th, html_escape(author_mail), td_th)
|
||||||
elif field == 'date':
|
elif field == 'date':
|
||||||
ret += '<%s>%s</%s>' % (td_th, date, td_th)
|
ret += '<%s>%s</%s>' % (td_th, date, td_th)
|
||||||
|
|
||||||
|
@ -609,7 +686,7 @@ if __name__ == '__main__':
|
||||||
file_no = 0
|
file_no = 0
|
||||||
for error in contentHandler.errors:
|
for error in contentHandler.errors:
|
||||||
filename = error['file']
|
filename = error['file']
|
||||||
if filename not in files.keys():
|
if filename not in files:
|
||||||
files[filename] = {
|
files[filename] = {
|
||||||
'errors': [], 'htmlfile': str(file_no) + '.html'}
|
'errors': [], 'htmlfile': str(file_no) + '.html'}
|
||||||
file_no = file_no + 1
|
file_no = file_no + 1
|
||||||
|
@ -718,9 +795,12 @@ if __name__ == '__main__':
|
||||||
|
|
||||||
stats_count = 0
|
stats_count = 0
|
||||||
stats = []
|
stats = []
|
||||||
|
filter_enabled = {}
|
||||||
for filename, data in sorted(files.items()):
|
for filename, data in sorted(files.items()):
|
||||||
for error in data['errors']:
|
for error in data['errors']:
|
||||||
stats.append(error['id']) # get the stats
|
stats.append(error['id']) # get the stats
|
||||||
|
filter_enabled[error['severity']] = True
|
||||||
|
filter_enabled['clang-tidy' if error['id'].startswith('clang-tidy-') else 'cppcheck'] = True
|
||||||
stats_count += 1
|
stats_count += 1
|
||||||
|
|
||||||
counter = Counter(stats)
|
counter = Counter(stats)
|
||||||
|
@ -744,7 +824,7 @@ if __name__ == '__main__':
|
||||||
stat_html.append(stat_fmt.format(_id, _id, dict(counter.most_common())[_id], _id))
|
stat_html.append(stat_fmt.format(_id, _id, dict(counter.most_common())[_id], _id))
|
||||||
|
|
||||||
output_file.write(HTML_HEAD % (options.title, '', options.title, ''))
|
output_file.write(HTML_HEAD % (options.title, '', options.title, ''))
|
||||||
output_file.write(HTML_SEVERITY_FILTER)
|
output_file.write(filter_bar(filter_enabled))
|
||||||
output_file.write(HTML_HEAD_END)
|
output_file.write(HTML_HEAD_END)
|
||||||
output_file.write(HTML_MENU.replace('id="menu"', 'id="menu_index"', 1).replace("Defects:", "Defect summary", 1) % (''))
|
output_file.write(HTML_MENU.replace('id="menu"', 'id="menu_index"', 1).replace("Defects:", "Defect summary", 1) % (''))
|
||||||
output_file.write('\n <label><input type="checkbox" class=\"idToggle\" onclick="toggleAll()" checked> Toggle all</label>')
|
output_file.write('\n <label><input type="checkbox" class=\"idToggle\" onclick="toggleAll()" checked> Toggle all</label>')
|
||||||
|
@ -763,8 +843,9 @@ if __name__ == '__main__':
|
||||||
|
|
||||||
for filename, data in sorted(files.items()):
|
for filename, data in sorted(files.items()):
|
||||||
file_error = filename in decode_errors or filename.endswith('*')
|
file_error = filename in decode_errors or filename.endswith('*')
|
||||||
|
is_file = filename != '' and not file_error
|
||||||
row_content = filename if file_error else "<a href=\"%s\">%s</a>" % (data['htmlfile'], filename)
|
row_content = filename if file_error else "<a href=\"%s\">%s</a>" % (data['htmlfile'], filename)
|
||||||
|
htmlfile = data.get('htmlfile') if is_file else None
|
||||||
|
|
||||||
output_file.write("\n <tbody class=\"fileEntry\">")
|
output_file.write("\n <tbody class=\"fileEntry\">")
|
||||||
output_file.write("\n <tr><td colspan=\"5\">%s</td></tr>" % row_content)
|
output_file.write("\n <tr><td colspan=\"5\">%s</td></tr>" % row_content)
|
||||||
|
@ -772,11 +853,10 @@ if __name__ == '__main__':
|
||||||
if filename in decode_errors:
|
if filename in decode_errors:
|
||||||
output_file.write("\n <tr><td colspan=\"5\">Could not generated due to UnicodeDecodeError</td></tr>")
|
output_file.write("\n <tr><td colspan=\"5\">Could not generated due to UnicodeDecodeError</td></tr>")
|
||||||
|
|
||||||
for error in sorted(data['errors'], key=lambda k: k['line']):
|
sorted_errors = sorted(data['errors'], key=lambda k: k['line'])
|
||||||
if add_author_information:
|
blame_data = git_blame(sorted_errors, source_dir, filename, blame_options) if add_author_information else []
|
||||||
git_blame_dict = git_blame(error['line'], source_dir, error['file'], blame_options)
|
for error in sorted_errors:
|
||||||
else:
|
git_blame_dict = blame_lookup(blame_data, error['line'])
|
||||||
git_blame_dict = {}
|
|
||||||
message_class = None
|
message_class = None
|
||||||
try:
|
try:
|
||||||
if error['inconclusive'] == 'true':
|
if error['inconclusive'] == 'true':
|
||||||
|
@ -794,9 +874,7 @@ if __name__ == '__main__':
|
||||||
if error['severity'] in ['error', 'warning']:
|
if error['severity'] in ['error', 'warning']:
|
||||||
message_class = error['severity']
|
message_class = error['severity']
|
||||||
|
|
||||||
is_file = filename != '' and not file_error
|
|
||||||
line = error["line"] if is_file else ""
|
line = error["line"] if is_file else ""
|
||||||
htmlfile = data.get('htmlfile') if is_file else None
|
|
||||||
|
|
||||||
output_file.write(
|
output_file.write(
|
||||||
'\n %s' %
|
'\n %s' %
|
||||||
|
|
Loading…
Reference in New Issue