From d8e72fa46639bccaecb56b566acd62cfa516b6c1 Mon Sep 17 00:00:00 2001
From: David Malcolm <dmalcolm@redhat.com>
Date: Thu, 28 Feb 2019 13:54:58 -0500
Subject: [PATCH 013/124] FIXME: add contrib/diagnostic-json-to-html.py

---
 .../diagnostic-json-to-html-templates/index.html   | 195 +++++++++++++++++++++
 contrib/diagnostic-json-to-html.py                 | 139 +++++++++++++++
 2 files changed, 334 insertions(+)
 create mode 100644 contrib/diagnostic-json-to-html-templates/index.html
 create mode 100755 contrib/diagnostic-json-to-html.py

diff --git a/contrib/diagnostic-json-to-html-templates/index.html b/contrib/diagnostic-json-to-html-templates/index.html
new file mode 100644
index 0000000..e1d3cab
--- /dev/null
+++ b/contrib/diagnostic-json-to-html-templates/index.html
@@ -0,0 +1,195 @@
+{% macro class_for_kind(kind) -%}
+{# FIXME: this can probably be done via CSS #}
+{% if kind == 'error' %}
+badge badge-pill badge-danger
+{% elif kind == 'warning' %}
+badge badge-pill badge-warning
+{% else %}
+badge badge-pill badge-info
+{% endif %}
+{%- endmacro %}
+
+{# Display a list of group of events in a diagnostic path.  #}
+{% macro event_groups(groups) -%}
+{% for group in groups %}
+<div class="list-group-item list-group-item-action flex-column align-items-start">
+  {# {{ group | pprint }} #}
+  <h5 class="mb-1">{{ group['function'] }}</h5>
+  <div>Depth: {{ group['depth'] }}</div>
+
+  {% if group.max_line %}
+  <div>{{ group['file'] }}</div>
+
+  <table class="table table-striped table-bordered table-sm">
+    {% for line in range(group.min_line, group.max_line + 1) %}
+    <tr>
+      <td style="text-align:right"> {{ line }} </td>
+      <td style="padding: 0 0 0 0;">
+	{% set html_line = get_html_line(group['file'], line) %}
+	<pre class="highlight" style="padding: 0 0 0 0;">{{ html_line }}</pre>
+      </td>
+    </tr>
+    {% for event in group.events %}
+    {% if event['location']['line'] == line %}
+    <tr>
+      <td></td>
+      <td style="padding: 0 0 0 0;">
+	<pre class="highlight" style="padding: 0 0 0 0;">{{ ' ' * (event['location']['column'] - 1) }}<span class="badge badge-pill badge-primary">{{event['id']}}</span>{{ event['description'] }}</pre>
+      </td>
+    </tr>
+    {% endif %}
+    {% endfor %}
+    {% endfor %}
+  </table>
+  {% else %}
+
+  {# No location information #}
+  {% for event in group.events %}
+  <div>
+    <div><span class="badge badge-pill badge-primary">{{event['id']}}</span>{{ event['description'] }}</div>
+  </div>
+  {% endfor %}
+
+  {% endif %}
+
+  {# Recurse, so that the nesting of groups is reflected in the HTML.
+     Hopefully this lets us visualize the calls and returns in any
+     interprocedural paths.  #}
+  {{ event_groups(group.children) }}
+
+</div>
+{% endfor %}
+
+{%- endmacro %}
+
+{# Display one diagnostic.  #}
+{% macro diagnostic(d, id) -%}
+{# see https://getbootstrap.com/docs/4.0/components/list-group/#custom-content #}
+<div class="list-group-item list-group-item-action flex-column align-items-start">
+  <div class="d-flex w-100 justify-content-between">
+    <h5 class="mb-1">
+      <a href="#{{id}}">
+	<span class="{{class_for_kind(d['kind'])}}">{{ d['kind'] }}</span>
+	<span>: </span>
+	<span>{{ d['message'] }}</span>
+      </a>
+    </h5>
+    <span><a href="{{d['option_url']}}">{{ d['option'] }}</a></span>
+  </div>
+  {% if d['locations'] %}
+  <p class="mb-1">
+    <span>{{d['locations'][0]['caret']['file']}}</span><span>:</span>
+    <span>{{d['locations'][0]['caret']['line']}}</span><span>:</span>
+    <span>{{d['locations'][0]['caret']['column']}}</span>
+  </p>
+  {% endif %}
+
+  {# Display source code #}
+  {% if d['locations'] %}
+  {% set caret = d['locations'][0]['caret'] %}
+  {% set html_line = get_html_line(caret['file'], caret['line']) %}
+  <table class="table table-striped table-bordered table-sm">
+    <tr>
+      <td style="text-align:right"> {{ caret['line'] }} </td>
+      <td style="padding: 0 0 0 0;">
+	<pre class="highlight" style="padding: 0 0 0 0;">{{ html_line }}</pre>
+      </td>
+    </tr>
+    <tr>
+      <td style="text-align:right"></td>
+      <td style="padding: 0 0 0 0;">
+	<pre class="highlight" style="padding: 0 0 0 0;">{{ ' ' * (caret['column'] - 1) }}^</pre>
+      </td>
+    </tr>
+  </table>
+  {% endif %}
+
+  {# Display any path #}
+  {% if d['path'] %}
+  <div class="list-group-item list-group-item-action flex-column align-items-start">
+
+    Path of length {{ d['path']|length }}:
+
+    {# Which path-printing implementation to use?  #}
+    {% if True %}
+
+    {# Group the events within the path, and print them,
+       showing interprocedural nesting.  #}
+
+    {{ event_groups(path_to_tree(d['path'])) }}
+
+    {% else %}
+
+    {# Simpler implementation of path-printing: just a list of events #}
+    {% for event in d['path'] %}
+    <div class="list-group-item list-group-item-action flex-column align-items-start">
+      <h5 class="mb-1">{{ event['description'] }}</h5>
+      <!-- {{ event|pprint }} -->
+      {% set location = event['location'] %}
+      <div>{{ location }}</div>
+      <div>{{ event['function'] }}</div>
+      {% if location %}
+      {% set html_line = get_html_line(location['file'], location['line']) %}
+      <table class="table table-striped table-bordered table-sm">
+	<tr>
+	  <td style="text-align:right"> {{ location['line'] }} </td>
+	  <td style="padding: 0 0 0 0;">
+	    <pre class="highlight" style="padding: 0 0 0 0;">{{ html_line }}</pre>
+	  </td>
+	</tr>
+	<tr>
+	  <td style="padding: 0 0 0 0;">
+	    <pre class="highlight" style="padding: 0 0 0 0;">{{ ' ' * (location['column'] - 1) }}^</pre>
+	  </td>
+	</tr>
+      </table>
+      {% endif %}
+    </div>
+    {% endfor %}
+
+    {% endif %}
+  </div>
+  {% endif %}
+
+  {# Recursively display child diagnostics.  #}
+  {% for child in d['children'] %}
+  {{ diagnostic(child, id + '.' + loop.index0|string) }}
+  {% endfor %}
+</div>
+{%- endmacro %}
+
+
+<!DOCTYPE html>
+<html lang="en">
+<head>
+  <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+
+  <!-- Bootstrap CSS -->
+  <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.1/css/bootstrap.min.css" integrity="sha384-WskhaSGFgHYWDcbwN70/dfYBj47jz9qbsMId/iRN3ewGhXQFZCSftd1LZCfmhktB" crossorigin="anonymous">
+
+    <style>
+      {{ css }}
+    </style>
+
+    {% block head %}
+    <title>{% block title %}{% endblock %} - GCC diagnostics</title>
+    {% endblock %}
+</head>
+<body>
+    <div id="content">
+      <div class="list-group">
+      {% for d in diagnostics %}
+      {{ diagnostic(d, loop.index0|string) }}
+      {% endfor %}
+    </div>
+    <div id="footer">
+    </div>
+
+    <!-- Bootstrap JS -->
+    <script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script>
+    <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.3/umd/popper.min.js" integrity="sha384-ZMP7rVo3mIykV+2+9J3UJ46jBk0WLaUAdn689aCwoqbBJiSnjAK/l8WvCWPIPm49" crossorigin="anonymous"></script>
+    <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.1.1/js/bootstrap.min.js" integrity="sha384-smHYKdLADwkXOn1EmN1qk/HfnUcbVRZyYmZ4qpPea6sjB/pTJ0euyQp0Mk8ck+5T" crossorigin="anonymous"></script>
+
+</body>
+</html>
+
diff --git a/contrib/diagnostic-json-to-html.py b/contrib/diagnostic-json-to-html.py
new file mode 100755
index 0000000..c2c9e53
--- /dev/null
+++ b/contrib/diagnostic-json-to-html.py
@@ -0,0 +1,139 @@
+#!/usr/bin/python3
+
+import linecache
+import json
+import os
+import sys
+
+from jinja2 import Environment, select_autoescape, FileSystemLoader, Markup
+import pygments.lexers
+import pygments.styles
+import pygments.formatters
+
+def get_template_path():
+    this_file = os.path.realpath(__file__)
+    SUBDIR = 'diagnostic-json-to-html-templates'
+    dirname = os.path.join(os.path.dirname(this_file), SUBDIR)
+    return dirname
+
+formatter = pygments.formatters.HtmlFormatter()
+
+def get_html_lines(sourcefile):
+    with open(sourcefile) as f:
+        code = f.read()
+
+    style = pygments.styles.get_style_by_name('default')
+    lexer = pygments.lexers.guess_lexer_for_filename(sourcefile, code)
+
+    # Use pygments to convert it all to HTML:
+    code_as_html = pygments.highlight(code, lexer, formatter)
+
+    if 0:
+        print(code_as_html)
+        print('*' * 76)
+        print(repr(code_as_html))
+        print('*' * 76)
+
+    EXPECTED_START = '<div class="highlight"><pre>'
+    assert code_as_html.startswith(EXPECTED_START)
+    code_as_html = code_as_html[len(EXPECTED_START):-1]
+
+    EXPECTED_END = '</pre></div>'
+    assert code_as_html.endswith(EXPECTED_END)
+    code_as_html = code_as_html[0:-len(EXPECTED_END)]
+
+    html_lines = [Markup(line) for line in code_as_html.splitlines()]
+    return html_lines
+
+html_line_cache = {}
+
+def get_html_line(sourcefile, line):
+    global html_line_cache
+    if sourcefile not in html_line_cache:
+        html_line_cache[sourcefile] = get_html_lines(sourcefile)
+    return html_line_cache[sourcefile][line - 1]
+
+def can_consolidate_events(e1, e2):
+    if 'location' not in e1 or 'location' not in e2:
+        return False
+    if e1['location']['file'] != e2['location']['file']:
+        return False
+    if e1['function'] != e2['function']:
+        return False
+    if e1['depth'] != e2['depth']:
+        return False
+    return True;
+
+def get_file(event):
+    if 'location' not in event:
+        return None
+    return event['location']['file']
+
+def get_line(event):
+    if 'location' not in event:
+        return None
+    return event['location']['line']
+
+def make_group(events):
+    g = {'file': get_file(events[0]),
+         'function': events[0]['function'],
+         'events': events,
+         'depth': events[0]['depth'],
+         'min_line': min(get_line(ev) for ev in events),
+         'max_line': max(get_line(ev) for ev in events)}
+    return g
+
+def path_to_group_list(path):
+    """
+    Given PATH, a list of events, generate a list of groups,
+    consolidating them by file/function.
+    """
+    groups = []
+    pending = []
+    for idx, event in enumerate(path):
+        event['id'] = idx + 1
+
+    for event in path:
+        if pending:
+            if can_consolidate_events(event, pending[0]):
+                pending.append(event)
+            else:
+                groups.append(make_group(pending))
+                pending = [event]
+        else:
+            pending = [event]
+    if pending:
+        groups.append(make_group(pending))
+    return groups
+
+def path_to_tree(path):
+    """
+    Given PATH, a list of events, generate a list of groups,
+    consolidating them by file/function, and nesting them
+    based on stack depth.
+    """
+    result = []
+    parent_for_depth = {}
+    current = None
+    for group in path_to_group_list(path):
+        group['children'] = []
+        depth = group['depth']
+        if depth > 0:
+            parent_for_depth[depth]['children'].append(group)
+        else:
+            result.append(group)
+        parent_for_depth[depth + 1] = group
+    return result
+
+env = Environment(
+    loader=FileSystemLoader(get_template_path()),
+    autoescape=select_autoescape(['html', 'xml']))
+
+env.globals['get_html_line'] = get_html_line
+env.globals['css'] = formatter.get_style_defs()
+env.globals['path_to_tree'] = path_to_tree
+
+data = json.load(sys.stdin)
+
+template = env.get_template('index.html')
+print(template.render(diagnostics=data))
-- 
1.8.5.3

