Commits

Ned Batchelder  committed ee491a0

Syntax coloring in the HTML reports.

  • Participants
  • Parent commits 3dabd73

Comments (0)

Files changed (9)

   for DecoratorTools (including TurboGears) projects.  Fixed `issue 12` and
   `issue 13`.
 
+- HTML reports now include syntax-colored Python source.
+
 - Programs that change directory will still write .coverage files in the
   directory where execution started.  Fixed `issue 24`_.
   
 + HTML report
     - Package navigation.
     - Rolled-up statistics.
-    - Syntax coloring in HTML report.
+    + Syntax coloring in HTML report.
     + Dynamic effects in HTML report.
     + Footer in reports pointing to coverage home page.
     + Baseline grid for linenumber font.
     - Generate new sample_html to get the latest, incl footer version number:
         cd C:\ned\cog\trunk
         rmdir/s/q htmlcov
-        coverage -e -x cogapp\test_cogapp.py CogTestsInMemory
-        coverage -b -i -d htmlcov
+        coverage run cogapp\test_cogapp.py CogTestsInMemory
+        coverage html -i -d htmlcov
         copy htmlcov\*.* C:\ned\coverage\trunk\doc\sample_html
     - Build and publish docs:
         $ make px publish

File coverage/html.py

 """HTML reporting for Coverage."""
 
-import os, re, shutil
+import keyword, os, re, token, tokenize, shutil
 from coverage import __version__    # pylint: disable-msg=W0611
+from coverage.backward import StringIO   # pylint: disable-msg=W0622
 from coverage.report import Reporter
 from coverage.templite import Templite
 
     return open(data_filename(fname)).read()
     
 
+def phys_tokens(toks):
+    """Return all physical tokens, even line continuations.
+    
+    tokenize.generate_tokens() doesn't return a token for the backslash that
+    continues lines.  This wrapper provides those tokens so that we can
+    re-create a faithful representation of the original source.
+    
+    Returns the same values as generate_tokens()
+    
+    """
+    last_line = None
+    last_lineno = -1
+    for toktype, ttext, (slineno, scol), (elineno, ecol), ltext in toks:
+        if last_lineno != elineno:
+            if last_line and last_line[-2:] == "\\\n":
+                if toktype != token.STRING:
+                    ccol = len(last_line.split("\n")[-2]) - 1
+                    yield (
+                        99999, "\\\n",
+                        (slineno, ccol), (slineno, ccol+2),
+                        last_line
+                        )
+            last_line = ltext
+        yield toktype, ttext, (slineno, scol), (elineno, ecol), ltext
+        last_lineno = elineno
+
+
 class HtmlReporter(Reporter):
     """HTML reporting."""
     
     def html_file(self, cu, statements, excluded, missing):
         """Generate an HTML file for one source file."""
         
-        source = cu.source_file()
-        source_lines = source.readlines()
+        source = cu.source_file().read().expandtabs(4)
+        source_lines = source.split("\n")
         
-        n_lin = len(source_lines)
         n_stm = len(statements)
         n_exc = len(excluded)
         n_mis = len(missing)
         c_exc = " exc"
         c_mis = " mis"
         
+        ws_tokens = [token.INDENT, token.DEDENT, token.NEWLINE, tokenize.NL]
         lines = []
-        for lineno, line in enumerate(source_lines):
-            lineno += 1     # enumerate is 0-based, lines are 1-based.
-            
-            css_class = ""
-            if lineno in statements:
-                css_class += " stm"
-                if lineno not in missing and lineno not in excluded:
-                    css_class += c_run
-            if lineno in excluded:
-                css_class += c_exc
-            if lineno in missing:
-                css_class += c_mis
-                
-            lineinfo = {
-                'text': line,
-                'number': lineno,
-                'class': css_class.strip() or "pln"
-            }
-            lines.append(lineinfo)
+        line = []
+        lineno = 1
+        col = 0
+        tokgen = tokenize.generate_tokens(StringIO(source).readline)
+        for toktype, ttext, (_, scol), (_, ecol), _ in phys_tokens(tokgen):
+            mark_start = True
+            for part in re.split('(\n)', ttext):
+                if part == '\n':
+
+                    line_class = ""
+                    if lineno in statements:
+                        line_class += " stm"
+                        if lineno not in missing and lineno not in excluded:
+                            line_class += c_run
+                    if lineno in excluded:
+                        line_class += c_exc
+                    if lineno in missing:
+                        line_class += c_mis
+                        
+                    lineinfo = {
+                        'html': "".join(line),
+                        'number': lineno,
+                        'class': line_class.strip() or "pln"
+                    }
+                    lines.append(lineinfo)
+                    
+                    line = []
+                    lineno += 1
+                    col = 0
+                    mark_end = False
+                elif part == '':
+                    mark_end = False
+                elif toktype in ws_tokens:
+                    mark_end = False
+                else:
+                    if mark_start and scol > col:
+                        line.append(escape(" " * (scol - col)))
+                        mark_start = False
+                    css_class = tokenize.tok_name.get(toktype, 'xx').lower()[:3]
+                    if toktype == token.NAME and keyword.iskeyword(ttext):
+                        css_class = "key"
+                    tok_html = escape(part) or ' '
+                    line.append(
+                        "<span class='%s'>%s</span>" % (css_class, tok_html)
+                        )
+                    mark_end = True
+                scol = 0
+            if mark_end:
+                col = ecol
 
         # Write the HTML page for this file.
         html_filename = cu.flat_rootname() + ".html"
 def escape(t):
     """HTML-escape the text in t."""
     return (t
-            # Change all tabs to 4 spaces.
-            .expandtabs(4)
             # Convert HTML special chars into HTML entities.
             .replace("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;")
             .replace("'", "&#39;").replace('"', "&quot;")

File coverage/htmlfiles/pyfile.html

 </td>
 <td class='text' valign='top'>
     {% for line in lines %}
-    <p class='{{line.class}}'>{{line.text.rstrip|escape|not_empty}}</p>
+    <p class='{{line.class}}'>{{line.html}}<span class="strut">&nbsp;</span></p>
     {% endfor %}
 </td>
 </tr>

File coverage/htmlfiles/style.css

     background: inherit;
     }
 
+/* Syntax coloring */
+.text .com {
+	color: green;
+	font-style: italic;
+	line-height: 1px;
+	}
+.text .key {
+	font-weight: bold;
+	line-height: 1px;
+	}
+.text .str {
+	color: #000080;
+	}
+
 /* index styles */
 #index td, #index th {
     text-align: right;

File test/farm/html/gold_a/a.html

 <body>
 <div id='header'>
     <div class='content'>
-        <h1>Coverage for <b>c:\ned\coverage\trunk\test\farm\html\src\a.py</b>:
+        <h1>Coverage for <b>a</b> :
             <span class='pc_cov'>67%</span>
         </h1>
         <h2 class='stats'>
     
 </td>
 <td class='text' valign='top'>
-<p class='pln'># A test file for HTML reporting by coverage.</p>
-<p class='pln'>&nbsp;</p>
-<p class='stm run hide'>if 1 &lt; 2:</p>
-<p class='pln'>&nbsp; &nbsp; # Needed a &lt; to look at HTML entities.</p>
-<p class='stm run hide'>&nbsp; &nbsp; a = 3</p>
-<p class='pln'>else:</p>
-<p class='stm mis'>&nbsp; &nbsp; a = 4</p>
+<p class='pln'><span class='com'># A test file for HTML reporting by coverage.</span><span class="strut">&nbsp;</span></p>
+<p class='pln'><span class="strut">&nbsp;</span></p>
+<p class='stm run hide'><span class='key'>if</span> <span class='num'>1</span> <span class='op'>&lt;</span> <span class='num'>2</span><span class='op'>:</span><span class="strut">&nbsp;</span></p>
+<p class='pln'>&nbsp; &nbsp; <span class='com'># Needed a &lt; to look at HTML entities.</span><span class="strut">&nbsp;</span></p>
+<p class='stm run hide'>&nbsp; &nbsp; <span class='nam'>a</span> <span class='op'>=</span> <span class='num'>3</span><span class="strut">&nbsp;</span></p>
+<p class='pln'><span class='key'>else</span><span class='op'>:</span><span class="strut">&nbsp;</span></p>
+<p class='stm mis'>&nbsp; &nbsp; <span class='nam'>a</span> <span class='op'>=</span> <span class='num'>4</span><span class="strut">&nbsp;</span></p>
     
 </td>
 </tr>

File test/farm/html/gold_a/index.html

 <div id='footer'>
     <div class='content'>
         <p>
-            <a class='nav' href='http://bitbucket.org/ned/coveragepy/'>coverage.py v3.0b2</a>
+            <a class='nav' href='http://nedbatchelder.com/code/coverage'>coverage v3.1b1</a>
         </p>
     </div>
 </div>

File test/farm/html/run_a.py

 #   and check that certain key strings are in the output.
 compare("html", "gold_a", size_within=10)
 contains("html/a.html",
-    ">if 1 &lt; 2:<",
-    "&nbsp; &nbsp; a = 3",
+    "<span class='key'>if</span> <span class='num'>1</span> <span class='op'>&lt;</span> <span class='num'>2</span>",
+    "&nbsp; &nbsp; <span class='nam'>a</span> <span class='op'>=</span> <span class='num'>3</span>",
     "<span class='pc_cov'>67%</span>"
     )
 contains("html/index.html",

File test/farm/html/run_tabbed.py

 contains("src/tabbed.py", "\tif x:\t\t\t\t\t\t# look nice")
 
 contains("html/tabbed.html",
-    "<p class='stm run hide'>&nbsp; &nbsp; if x:&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp; # look nice</p>")
+    ">&nbsp; &nbsp; <span class='key'>if</span> <span class='nam'>x</span>"
+    "<span class='op'>:</span>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;"
+    " &nbsp; &nbsp; &nbsp; &nbsp;&nbsp; <span class='com'># look nice</span>"
+    )
+
 doesnt_contain("html/tabbed.html", "\t")
 clean("html")