diff service/mqtt_to_rdf/structured_log.py @ 1694:73abfd4cf5d0

new html log and other refactoring as i work on the advanceTheStack problems https://bigasterisk.com/post/inference/2021-09-27_11-11.png
author drewp@bigasterisk.com
date Mon, 27 Sep 2021 11:22:09 -0700
parents
children
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/service/mqtt_to_rdf/structured_log.py	Mon Sep 27 11:22:09 2021 -0700
@@ -0,0 +1,200 @@
+import re
+from pathlib import Path
+from typing import List, Optional, Union, cast
+
+import simple_html.render
+from rdflib import Graph
+from rdflib.term import Node
+from simple_html.nodes import (SafeString, body, div, head, hr, html, span,
+                               style, table, td, tr)
+
+from candidate_binding import CandidateBinding
+from inference_types import Triple
+from stmt_chunk import Chunk, ChunkedGraph
+
+CSS = SafeString('''
+@import url('https://fonts.googleapis.com/css2?family=Oxygen+Mono&display=swap');
+
+* {
+    font-family: 'Oxygen Mono', monospace;
+    font-size: 10px;
+}
+.arrow {
+    font-size: 350%;
+    vertical-align: middle;
+}
+table {
+    border-collapse: collapse;
+}
+td {
+    vertical-align: top;
+    border: 1px solid gray;
+    padding: 2px;
+}
+.consider.isNew-False {
+    opacity: .3;
+}
+.iteration {
+   font-size: 150%;
+    padding: 20px 0;
+    background: #c7dec7;
+}
+.timetospin {
+    font-size: 150%;
+    margin-top: 20 px ;
+    padding: 10 px 0;
+    background: #86a886;
+}
+.looper {
+    background: #e2e2e2;
+}
+.alignedWorkingSetChunk tr:nth-child(even) {
+    background: #bddcbd;
+}
+.alignedWorkingSetChunk tr:nth-child(odd) {
+    background: hsl(120deg 31% 72%);
+}
+.highlight, .alignedWorkingSetChunk tr.highlight  {
+    background: #ffffd6;
+    outline: 1px solid #d2d200;
+    padding: 2px;
+    line-height: 17px;
+}
+.node { padding: 0 2px; }
+.URIRef { background: #e0b3b3; }
+.Literal { background: #eecc95 }
+.Variable { background: #aaaae8; }
+.BNode { background: orange; }
+.say {
+    white-space: pre-wrap;
+}
+.say.now.past.end {
+    color: #b82c08;
+}
+.say.restarts {
+    color: #51600f;
+}
+.say.advance.start {
+    margin-top: 5px;
+}
+.say.finished {
+    border-bottom: 1px solid gray;
+   display: inline-block;
+   margin-bottom: 8px;
+}
+''')
+
+
+class StructuredLog:
+    workingSet: Graph
+
+    def __init__(self, output: Path):
+        self.output = output
+        self.steps = []
+
+    def say(self, line):
+        classes = ['say'] + [c for c in re.split(r'\s+|\W|(\d+)', line) if c]
+        cssList = ' '.join(classes)
+        self.steps.append(div.attrs(('class', cssList))(line))
+
+    def startIteration(self, num: int):
+        self.steps.extend([hr(), div.attrs(('class', 'iteration'))(f"iteration {num}")])
+
+    def rule(self, workingSet: Graph, i: int, rule):
+        self.steps.append(htmlGraph('working set', self.workingSet))
+        self.steps.append(f"try rule {i}")
+        self.steps.append(htmlRule(rule))
+
+    def foundBinding(self, bound):
+        self.steps.append(div('foundBinding', htmlBinding(bound.binding)))
+
+    def looperConsider(self, looper, newBinding, fullBinding, isNew):
+        self.steps.append(
+            table.attrs(('class', f'consider isNew-{isNew}'))(tr(
+                td(htmlChunkLooper(looper, showBindings=False)),
+                td(div('newBinding', htmlBinding(newBinding))),
+                td(div('fullBinding', htmlBinding(fullBinding))),
+                td(f'{isNew=}'),
+            )))
+
+    def odometer(self, chunkStack):
+        self.steps.append(
+            table(
+                tr(*[
+                    td(
+                        table.attrs(('class', 'looper'))(
+                            tr(
+                                td(htmlChunkLooper(looper, showBindings=False)),  #
+                                td(div('newBinding'),
+                                   htmlBinding(looper.localBinding()) if not looper.pastEnd() else '(pastEnd)'),  #
+                                td(div('fullBinding'),
+                                   htmlBinding(looper.currentBinding()) if not looper.pastEnd() else ''),  #
+                            ))) for looper in chunkStack
+                ])))
+
+    def render(self):
+
+        with open(self.output, 'w') as out:
+            out.write(simple_html.render.render(html(head(style(CSS)), body(div(*self.steps)))))
+
+
+def htmlRule(r):
+    return table(tr(td(htmlGraph('lhsGraph', r.lhsGraph)), td(htmlGraph('rhsGraph', r.rhsGraph))))
+
+
+def htmlGraph(label: str, g: Graph):
+    return div(label, table(*[htmlStmtRow(s) for s in sorted(g)]))
+
+
+def htmlStmtRow(s: Triple):
+    return tr(td(htmlTerm(s[0])), td(htmlTerm(s[1])), td(htmlTerm(s[2])))
+
+
+def htmlTerm(t: Union[Node, List[Node]]):
+    if isinstance(t, list):
+        return span('( ', *[htmlTerm(x) for x in t], ')')
+    return span.attrs(('class', 'node ' + t.__class__.__name__))(repr(t))
+
+
+def htmlBinding(b: CandidateBinding):
+    return table(*[tr(td(htmlTerm(k)), td(htmlTerm(v))) for k, v in sorted(b.binding.items())])
+
+
+def htmlChunkLooper(looper, showBindings=True):
+    alignedMatches = []
+    for i, arc in enumerate(looper._alignedMatches):
+        hi = arc.workingSetChunk == looper.currentSourceChunk
+        alignedMatches.append(
+            tr.attrs(('class', 'highlight' if hi else ''))(
+                td(span.attrs(('class', 'arrow'))('➢' if hi else ''), str(i)),  #
+                td(htmlChunk(arc.workingSetChunk))))
+    return table(
+        tr(
+            td(div(repr(looper)), div(f"prev = {looper.prev}")),
+            td(
+                div('lhsChunk'),
+                htmlChunk(looper.lhsChunk),  #
+                div('alignedMatches'),
+                table.attrs(('class', 'alignedWorkingSetChunk'))(*alignedMatches)  #
+            ),
+            td('localBinding', htmlBinding(looper.localBinding())) if showBindings else '',
+            td('currentBinding', htmlBinding(looper.currentBinding())) if showBindings else '',
+        ))
+
+
+def htmlChunkedGraph(g: ChunkedGraph, highlightChunk: Optional[Chunk] = None):
+    return table(
+        tr(td('staticChunks'), td(*[div(htmlChunk(ch, ch == highlightChunk)) for ch in sorted(g.staticChunks)])),
+        tr(td('patternChunks'), td(*[div(htmlChunk(ch, ch == highlightChunk)) for ch in sorted(g.patternChunks)])),
+        tr(td('chunksUsedByFuncs'), td(*[div(htmlChunk(ch, ch == highlightChunk)) for ch in sorted(g.chunksUsedByFuncs)])),
+    )
+
+
+def htmlChunk(ch: Chunk, highlight=False):
+    return span.attrs(('class', 'highlight' if highlight else ''))(
+        'subj=',
+        htmlTerm(ch.primary[0] if ch.primary[0] is not None else cast(List[Node], ch.subjList)),  #
+        ' pred=',
+        htmlTerm(ch.predicate),  #
+        ' obj=',
+        htmlTerm(ch.primary[2] if ch.primary[2] is not None else cast(List[Node], ch.objList)))