summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNed Batchelder <ned@nedbatchelder.com>2009-09-23 11:00:53 -0400
committerNed Batchelder <ned@nedbatchelder.com>2009-09-23 11:00:53 -0400
commite3ff72a32401f97b4e3f8e627780c87366d62414 (patch)
tree9b965e865b7f3da28933dae40bd73a7b539a39c4
parent0b96d1ba8b0fb03628a57e255b39243c66751038 (diff)
downloadpython-coveragepy-git-e3ff72a32401f97b4e3f8e627780c87366d62414.tar.gz
Syntax coloring in the HTML reports.
-rw-r--r--CHANGES.txt2
-rw-r--r--TODO.txt6
-rw-r--r--coverage/html.py106
-rw-r--r--coverage/htmlfiles/pyfile.html2
-rw-r--r--coverage/htmlfiles/style.css14
-rw-r--r--test/farm/html/gold_a/a.html16
-rw-r--r--test/farm/html/gold_a/index.html2
-rw-r--r--test/farm/html/run_a.py4
-rw-r--r--test/farm/html/run_tabbed.py6
9 files changed, 117 insertions, 41 deletions
diff --git a/CHANGES.txt b/CHANGES.txt
index f59e4e77..0d8186ce 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -18,6 +18,8 @@ Version 3.1, unreleased
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`_.
diff --git a/TODO.txt b/TODO.txt
index da2f35d9..0d97cba3 100644
--- a/TODO.txt
+++ b/TODO.txt
@@ -71,7 +71,7 @@ x Tricky swapping of collector like figleaf, pycov, et al. (Don't need to do
+ 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.
@@ -183,8 +183,8 @@ x Tests about the "import __main__" in cmdline.py
- 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
diff --git a/coverage/html.py b/coverage/html.py
index c0984084..e05b0066 100644
--- a/coverage/html.py
+++ b/coverage/html.py
@@ -1,7 +1,8 @@
"""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
@@ -18,6 +19,33 @@ def data(fname):
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."""
@@ -57,10 +85,9 @@ class HtmlReporter(Reporter):
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)
@@ -75,26 +102,57 @@ class HtmlReporter(Reporter):
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 '&nbsp;'
+ 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"
@@ -139,8 +197,6 @@ class HtmlReporter(Reporter):
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;")
diff --git a/coverage/htmlfiles/pyfile.html b/coverage/htmlfiles/pyfile.html
index d1810fa1..a7cd1d3c 100644
--- a/coverage/htmlfiles/pyfile.html
+++ b/coverage/htmlfiles/pyfile.html
@@ -43,7 +43,7 @@ function toggle_lines(btn, cls) {
</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>
diff --git a/coverage/htmlfiles/style.css b/coverage/htmlfiles/style.css
index d9d324c4..ef57755b 100644
--- a/coverage/htmlfiles/style.css
+++ b/coverage/htmlfiles/style.css
@@ -124,6 +124,20 @@ td.text {
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;
diff --git a/test/farm/html/gold_a/a.html b/test/farm/html/gold_a/a.html
index 6cc0b908..b29831b6 100644
--- a/test/farm/html/gold_a/a.html
+++ b/test/farm/html/gold_a/a.html
@@ -21,7 +21,7 @@ function toggle_lines(btn, cls) {
<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'>
@@ -47,13 +47,13 @@ function toggle_lines(btn, cls) {
</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>
diff --git a/test/farm/html/gold_a/index.html b/test/farm/html/gold_a/index.html
index 18b8af5b..c8a0ecc7 100644
--- a/test/farm/html/gold_a/index.html
+++ b/test/farm/html/gold_a/index.html
@@ -45,7 +45,7 @@
<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>
diff --git a/test/farm/html/run_a.py b/test/farm/html/run_a.py
index ab0da42e..4a8a0514 100644
--- a/test/farm/html/run_a.py
+++ b/test/farm/html/run_a.py
@@ -13,8 +13,8 @@ runfunc(html_it, rundir="src")
# 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",
diff --git a/test/farm/html/run_tabbed.py b/test/farm/html/run_tabbed.py
index c49bdfbd..88ffcaef 100644
--- a/test/farm/html/run_tabbed.py
+++ b/test/farm/html/run_tabbed.py
@@ -13,6 +13,10 @@ runfunc(html_it, rundir="src")
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")