rearrange query/browse output to be more sensible
authorEvan Martin <martine@danga.com>
Tue, 24 Jan 2012 23:56:15 +0000 (15:56 -0800)
committerEvan Martin <martine@danga.com>
Tue, 24 Jan 2012 23:56:15 +0000 (15:56 -0800)
src/browse.py
src/ninja.cc

index ca95197..17e67cf 100755 (executable)
@@ -24,36 +24,66 @@ import BaseHTTPServer
 import subprocess
 import sys
 import webbrowser
+from collections import namedtuple
 
-def match_strip(prefix, line):
+Node = namedtuple('Node', ['inputs', 'rule', 'target', 'outputs'])
+
+# Ideally we'd allow you to navigate to a build edge or a build node,
+# with appropriate views for each.  But there's no way to *name* a build
+# edge so we can only display nodes.
+#
+# For a given node, it has at most one input edge, which has n
+# different inputs.  This becomes node.inputs.  (We leave out the
+# outputs of the input edge due to what follows.)  The node can have
+# multiple dependent output edges.  Rather than attempting to display
+# those, they are summarized by taking the union of all their outputs.
+#
+# This means there's no single view that shows you all inputs and outputs
+# of an edge.  But I think it's less confusing than alternatives.
+
+def match_strip(line, prefix):
     if not line.startswith(prefix):
-        print prefix, line
-        assert line.startswith(prefix)
-    return line[len(prefix):]
+        return (False, line)
+    return (True, line[len(prefix):])
 
 def parse(text):
-    lines = text.split('\n')
-    node = lines.pop(0)
-    node = node[:-1]  # strip trailing colon
-
-    input = []
-    if lines and lines[0].startswith('  input:'):
-        input.append(match_strip('  input: ', lines.pop(0)))
-        while lines and lines[0].startswith('    '):
-            input.append(lines.pop(0).strip())
+    lines = iter(text.split('\n'))
 
+    target = None
+    rule = None
+    inputs = []
     outputs = []
-    while lines:
-        output = []
-        output.append(match_strip('  output: ', lines.pop(0)))
-        while lines and lines[0].startswith('    '):
-            output.append(lines.pop(0).strip())
-        outputs.append(output)
-
-    return (node, input, outputs)
 
-def generate_html(data):
-    node, input, outputs = data
+    try:
+        target = lines.next()[:-1]  # strip trailing colon
+
+        line = lines.next()
+        (match, rule) = match_strip(line, '  input: ')
+        if match:
+            (match, line) = match_strip(lines.next(), '    ')
+            while match:
+                type = None
+                (match, line) = match_strip(line, '| ')
+                if match:
+                    type = 'implicit'
+                (match, line) = match_strip(line, '|| ')
+                if match:
+                    type = 'order-only'
+                inputs.append((line, type))
+                (match, line) = match_strip(lines.next(), '    ')
+
+        match, _ = match_strip(line, '  outputs:')
+        if match:
+            (match, line) = match_strip(lines.next(), '    ')
+            while match:
+                outputs.append(line)
+                (match, line) = match_strip(lines.next(), '    ')
+    except StopIteration:
+        pass
+
+    return Node(inputs, rule, target, outputs)
+
+def generate_html(node):
     print '''<!DOCTYPE html>
 <style>
 body {
@@ -63,47 +93,42 @@ body {
 }
 h1 {
     font-weight: normal;
+    font-size: 140%;
     text-align: center;
     margin: 0;
 }
 h2 {
     font-weight: normal;
+    font-size: 120%;
 }
 tt {
     font-family: WebKitHack, monospace;
+    white-space: nowrap;
 }
-ul {
-    margin-top: 0;
-    padding-left: 20px;
+.filelist {
+  -webkit-columns: auto 2;
 }
 </style>'''
-    print '<table width=500><tr><td colspan=3>'
-    print '<h1><tt>%s</tt></h1>' % node
-    print '</td></tr>'
-
-    print '<tr><td valign=top>'
-    if input:
-        print '<h2>built by rule: <tt>%s</tt></h2>' % input[0]
-        if len(input) > 0:
-            print 'inputs:'
-            print '<ul>'
-            for i in input[1:]:
-                print '<li><tt><a href="?%s">%s</a></tt></li>' % (i, i)
-            print '</ul>'
-    else:
-        print '<h2>no input rule</h2>'
-    print '</td>'
-    print '<td width=50>&nbsp;</td>'
-
-    print '<td valign=top>'
-    print '<h2>dependents</h2>'
-    for output in outputs:
-        print '<tt>%s</tt>' % output[0]
-        print '<ul>'
-        for i in output[1:]:
-            print '<li><tt><a href="?%s">%s</a></tt></li>' % (i, i)
-        print '</ul>'
-    print '</td></tr></table>'
+
+    print '<h1><tt>%s</tt></h1>' % node.target
+
+    if node.inputs:
+        print '<h2>target is built using rule <tt>%s</tt> of</h2>' % node.rule
+        if len(node.inputs) > 0:
+            print '<div class=filelist>'
+            for input, type in sorted(node.inputs):
+                extra = ''
+                if type:
+                    extra = ' (%s)' % type
+                print '<tt><a href="?%s">%s</a>%s</tt><br>' % (input, input, extra)
+            print '</div>'
+
+    if node.outputs:
+        print '<h2>dependent edges build:</h2>'
+        print '<div class=filelist>'
+        for output in sorted(node.outputs):
+            print '<tt><a href="?%s">%s</a></tt><br>' % (output, output)
+        print '</div>'
 
 def ninja_dump(target):
     proc = subprocess.Popen([sys.argv[1], '-t', 'query', target],
index 4e750f4..1822e54 100644 (file)
@@ -224,24 +224,7 @@ int ToolQuery(Globals* globals, int argc, char* argv[]) {
   }
   for (int i = 0; i < argc; ++i) {
     Node* node = globals->state->LookupNode(argv[i]);
-    if (node) {
-      printf("%s:\n", argv[i]);
-      if (node->in_edge()) {
-        printf("  input: %s\n", node->in_edge()->rule_->name().c_str());
-        for (vector<Node*>::iterator in = node->in_edge()->inputs_.begin();
-             in != node->in_edge()->inputs_.end(); ++in) {
-          printf("    %s\n", (*in)->path().c_str());
-        }
-      }
-      for (vector<Edge*>::const_iterator edge = node->out_edges().begin();
-           edge != node->out_edges().end(); ++edge) {
-        printf("  output: %s\n", (*edge)->rule_->name().c_str());
-        for (vector<Node*>::iterator out = (*edge)->outputs_.begin();
-             out != (*edge)->outputs_.end(); ++out) {
-          printf("    %s\n", (*out)->path().c_str());
-        }
-      }
-    } else {
+    if (!node) {
       Node* suggestion = globals->state->SpellcheckNode(argv[i]);
       if (suggestion) {
         printf("%s unknown, did you mean %s?\n",
@@ -251,6 +234,27 @@ int ToolQuery(Globals* globals, int argc, char* argv[]) {
       }
       return 1;
     }
+
+    printf("%s:\n", argv[i]);
+    if (Edge* edge = node->in_edge()) {
+      printf("  input: %s\n", edge->rule_->name().c_str());
+      for (int in = 0; in < (int)edge->inputs_.size(); in++) {
+        const char* label = "";
+        if (edge->is_implicit(in))
+          label = "| ";
+        else if (edge->is_order_only(in))
+          label = "|| ";
+        printf("    %s%s\n", label, edge->inputs_[in]->path().c_str());
+      }
+    }
+    printf("  outputs:\n");
+    for (vector<Edge*>::const_iterator edge = node->out_edges().begin();
+         edge != node->out_edges().end(); ++edge) {
+      for (vector<Node*>::iterator out = (*edge)->outputs_.begin();
+           out != (*edge)->outputs_.end(); ++out) {
+        printf("    %s\n", (*out)->path().c_str());
+      }
+    }
   }
   return 0;
 }