summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGES.txt3
-rw-r--r--coverage/config.py3
-rw-r--r--coverage/control.py11
-rw-r--r--coverage/html.py15
-rw-r--r--coverage/htmlfiles/index.html3
-rw-r--r--coverage/htmlfiles/pyfile.html3
-rw-r--r--doc/config.rst4
-rw-r--r--test/farm/html/gold_styled/a.html95
-rw-r--r--test/farm/html/gold_styled/extra.css1
-rw-r--r--test/farm/html/gold_styled/index.html89
-rw-r--r--test/farm/html/gold_styled/style.css275
-rw-r--r--test/farm/html/run_styled.py28
-rw-r--r--test/farm/html/src/extra.css1
-rw-r--r--test/test_config.py3
14 files changed, 531 insertions, 3 deletions
diff --git a/CHANGES.txt b/CHANGES.txt
index 27b8e8ad..c373b617 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -5,6 +5,9 @@ Change history for Coverage.py
Version 3.5.2b1
---------------
+- Custom CSS can be applied to the HTML report by specifying a CSS file as
+ the extra_css configuration value in the [html] section.
+
- Source files with custom encodings declared in a comment at the top are now
properly handled during reporting on Python 2. Python 3 always handled them
properly. This fixes `issue 157`_.
diff --git a/coverage/config.py b/coverage/config.py
index ef45ba5a..49d74e7a 100644
--- a/coverage/config.py
+++ b/coverage/config.py
@@ -52,6 +52,7 @@ class CoverageConfig(object):
# Defaults for [html]
self.html_dir = "htmlcov"
+ self.extra_css = None
# Defaults for [xml]
self.xml_output = "coverage.xml"
@@ -125,6 +126,8 @@ class CoverageConfig(object):
# [html]
if cp.has_option('html', 'directory'):
self.html_dir = cp.get('html', 'directory')
+ if cp.has_option('html', 'extra_css'):
+ self.extra_css = cp.get('html', 'extra_css')
# [xml]
if cp.has_option('xml', 'output'):
diff --git a/coverage/control.py b/coverage/control.py
index 7373b316..c21d885e 100644
--- a/coverage/control.py
+++ b/coverage/control.py
@@ -582,15 +582,22 @@ class coverage(object):
reporter.report(morfs, directory=directory)
def html_report(self, morfs=None, directory=None, ignore_errors=None,
- omit=None, include=None):
+ omit=None, include=None, extra_css=None):
"""Generate an HTML report.
+ The HTML is written to `directory`. The file "index.html" is the
+ overview starting point, with links to more detailed pages for
+ individual modules.
+
+ `extra_css` is a path to a file of other CSS to apply on the page.
+ It will be copied into the HTML directory.
+
See `coverage.report()` for other arguments.
"""
self.config.from_args(
ignore_errors=ignore_errors, omit=omit, include=include,
- html_dir=directory,
+ html_dir=directory, extra_css=extra_css,
)
reporter = HtmlReporter(self, self.config)
reporter.report(morfs)
diff --git a/coverage/html.py b/coverage/html.py
index af27edfa..f39bf949 100644
--- a/coverage/html.py
+++ b/coverage/html.py
@@ -58,6 +58,7 @@ class HtmlReporter(Reporter):
self.files = []
self.arcs = self.coverage.data.has_arcs()
self.status = HtmlStatus()
+ self.extra_css = None
def report(self, morfs):
"""Generate an HTML report for `morfs`.
@@ -78,6 +79,10 @@ class HtmlReporter(Reporter):
self.status.reset()
self.status.set_settings_hash(these_settings)
+ # The user may have extra CSS they want copied.
+ if self.config.extra_css:
+ self.extra_css = os.path.basename(self.config.extra_css)
+
# Process all the files.
self.report_files(self.html_file, morfs, self.config.html_dir)
@@ -91,12 +96,20 @@ class HtmlReporter(Reporter):
def make_local_static_report_files(self):
"""Make local instances of static files for HTML report."""
+ # The files we provide must always be copied.
for static in self.STATIC_FILES:
shutil.copyfile(
data_filename("htmlfiles/" + static),
os.path.join(self.directory, static)
)
+ # The user may have extra CSS they want copied.
+ if self.extra_css:
+ shutil.copyfile(
+ self.config.extra_css,
+ os.path.join(self.directory, self.extra_css)
+ )
+
def write_html(self, fname, html):
"""Write `html` to `fname`, properly encoded."""
fout = open(fname, "wb")
@@ -202,6 +215,7 @@ class HtmlReporter(Reporter):
# Write the HTML page for this file.
html_filename = flat_rootname + ".html"
html_path = os.path.join(self.directory, html_filename)
+ extra_css = self.extra_css
html = spaceless(self.source_tmpl.render(locals()))
if sys.version_info < (3, 0):
@@ -228,6 +242,7 @@ class HtmlReporter(Reporter):
arcs = self.arcs
totals = sum([f['nums'] for f in files])
+ extra_css = self.extra_css
self.write_html(
os.path.join(self.directory, "index.html"),
diff --git a/coverage/htmlfiles/index.html b/coverage/htmlfiles/index.html
index 04b314a3..c6d9eec0 100644
--- a/coverage/htmlfiles/index.html
+++ b/coverage/htmlfiles/index.html
@@ -4,6 +4,9 @@
<meta http-equiv='Content-Type' content='text/html; charset=utf-8'>
<title>Coverage report</title>
<link rel='stylesheet' href='style.css' type='text/css'>
+ {% if extra_css %}
+ <link rel='stylesheet' href='{{ extra_css }}' type='text/css'>
+ {% endif %}
<script type='text/javascript' src='jquery-1.4.3.min.js'></script>
<script type='text/javascript' src='jquery.tablesorter.min.js'></script>
<script type='text/javascript' src='jquery.hotkeys.js'></script>
diff --git a/coverage/htmlfiles/pyfile.html b/coverage/htmlfiles/pyfile.html
index ee0a3b1b..434edfdd 100644
--- a/coverage/htmlfiles/pyfile.html
+++ b/coverage/htmlfiles/pyfile.html
@@ -7,6 +7,9 @@
<meta http-equiv='X-UA-Compatible' content='IE=emulateIE7' />
<title>Coverage for {{cu.name|escape}}: {{nums.pc_covered_str}}%</title>
<link rel='stylesheet' href='style.css' type='text/css'>
+ {% if extra_css %}
+ <link rel='stylesheet' href='{{ extra_css }}' type='text/css'>
+ {% endif %}
<script type='text/javascript' src='jquery-1.4.3.min.js'></script>
<script type='text/javascript' src='jquery.hotkeys.js'></script>
<script type='text/javascript' src='jquery.isonscreen.js'></script>
diff --git a/doc/config.rst b/doc/config.rst
index 7a54eec5..5c7f861f 100644
--- a/doc/config.rst
+++ b/doc/config.rst
@@ -173,6 +173,10 @@ also apply to HTML output, where appropriate.
``directory`` (string, default "htmlcov"): where to write the HTML report files.
+``extra_css`` (string): the path to a file of CSS to apply to the HTML report.
+The file will be copied into the HTML output directory. Don't name it
+"style.css".
+
[xml]
-----
diff --git a/test/farm/html/gold_styled/a.html b/test/farm/html/gold_styled/a.html
new file mode 100644
index 00000000..c794525e
--- /dev/null
+++ b/test/farm/html/gold_styled/a.html
@@ -0,0 +1,95 @@
+<!doctype html PUBLIC "-//W3C//DTD html 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
+<html>
+<head>
+ <meta http-equiv='Content-Type' content='text/html; charset=utf-8'>
+
+
+ <meta http-equiv='X-UA-Compatible' content='IE=emulateIE7' />
+ <title>Coverage for a: 67%</title>
+ <link rel='stylesheet' href='style.css' type='text/css'>
+ <script type='text/javascript' src='jquery-1.4.3.min.js'></script>
+ <script type='text/javascript' src='jquery.hotkeys.js'></script>
+ <script type='text/javascript' src='jquery.isonscreen.js'></script>
+ <script type='text/javascript' src='coverage_html.js'></script>
+ <script type='text/javascript' charset='utf-8'>
+ jQuery(document).ready(coverage.pyfile_ready);
+ </script>
+</head>
+<body id='pyfile'>
+
+<div id='header'>
+ <div class='content'>
+ <h1>Coverage for <b>a</b> :
+ <span class='pc_cov'>67%</span>
+ </h1>
+ <img id='keyboard_icon' src='keybd_closed.png'>
+ <h2 class='stats'>
+ 3 statements
+ <span class='run hide_run shortkey_r' onclick='coverage.toggle_lines(this, "run")'>2 run</span>
+ <span class='mis shortkey_m' onclick='coverage.toggle_lines(this, "mis")'>1 missing</span>
+ <span class='exc shortkey_x' onclick='coverage.toggle_lines(this, "exc")'>0 excluded</span>
+
+ </h2>
+ </div>
+</div>
+
+<div class='help_panel'>
+ <img id='panel_icon' src='keybd_open.png'>
+<p class='legend'>Hot-keys on this page</p>
+ <div>
+<p class='keyhelp'>
+ <span class='key'>r</span>
+ <span class='key'>m</span>
+ <span class='key'>x</span>
+ <span class='key'>p</span> &nbsp; toggle line displays
+ </p>
+<p class='keyhelp'>
+ <span class='key'>j</span>
+ <span class='key'>k</span> &nbsp; next/prev highlighted chunk
+ </p>
+<p class='keyhelp'>
+ <span class='key'>0</span> &nbsp; (zero) top of page
+ </p>
+<p class='keyhelp'>
+ <span class='key'>1</span> &nbsp; (one) first highlighted chunk
+ </p>
+ </div>
+</div>
+
+<div id='source'>
+ <table cellspacing='0' cellpadding='0'>
+ <tr>
+ <td class='linenos' valign='top'>
+<p id='n1' class='pln'><a href='#n1'>1</a></p>
+<p id='n2' class='pln'><a href='#n2'>2</a></p>
+<p id='n3' class='stm run hide_run'><a href='#n3'>3</a></p>
+<p id='n4' class='pln'><a href='#n4'>4</a></p>
+<p id='n5' class='stm run hide_run'><a href='#n5'>5</a></p>
+<p id='n6' class='pln'><a href='#n6'>6</a></p>
+<p id='n7' class='stm mis'><a href='#n7'>7</a></p>
+
+ </td>
+ <td class='text' valign='top'>
+<p id='t1' class='pln'><span class='com'># A test file for HTML reporting by coverage.</span><span class='strut'>&nbsp;</span></p>
+<p id='t2' class='pln'><span class='strut'>&nbsp;</span></p>
+<p id='t3' class='stm run hide_run'><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 id='t4' class='pln'>&nbsp; &nbsp; <span class='com'># Needed a &lt; to look at HTML entities.</span><span class='strut'>&nbsp;</span></p>
+<p id='t5' class='stm run hide_run'>&nbsp; &nbsp; <span class='nam'>a</span> <span class='op'>=</span> <span class='num'>3</span><span class='strut'>&nbsp;</span></p>
+<p id='t6' class='pln'><span class='key'>else</span><span class='op'>:</span><span class='strut'>&nbsp;</span></p>
+<p id='t7' 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>
+ </table>
+</div>
+
+<div id='footer'>
+ <div class='content'>
+ <p>
+ <a class='nav' href='index.html'>&#xab; index</a> &nbsp; &nbsp; <a class='nav' href='http://nedbatchelder.com/code/coverage/3.5a1'>coverage.py v3.5a1</a>
+ </p>
+ </div>
+</div>
+
+</body>
+</html>
diff --git a/test/farm/html/gold_styled/extra.css b/test/farm/html/gold_styled/extra.css
new file mode 100644
index 00000000..46c41fcd
--- /dev/null
+++ b/test/farm/html/gold_styled/extra.css
@@ -0,0 +1 @@
+/* Doesn't matter what goes in here, it gets copied. */
diff --git a/test/farm/html/gold_styled/index.html b/test/farm/html/gold_styled/index.html
new file mode 100644
index 00000000..a821e9df
--- /dev/null
+++ b/test/farm/html/gold_styled/index.html
@@ -0,0 +1,89 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
+<html>
+<head>
+ <meta http-equiv='Content-Type' content='text/html; charset=utf-8'>
+ <title>Coverage report</title>
+ <link rel='stylesheet' href='style.css' type='text/css'>
+ <script type='text/javascript' src='jquery-1.4.3.min.js'></script>
+ <script type='text/javascript' src='jquery.tablesorter.min.js'></script>
+ <script type='text/javascript' src='jquery.hotkeys.js'></script>
+ <script type='text/javascript' src='coverage_html.js'></script>
+ <script type='text/javascript' charset='utf-8'>
+ jQuery(document).ready(coverage.index_ready);
+ </script>
+</head>
+<body id='indexfile'>
+
+<div id='header'>
+ <div class='content'>
+ <h1>Coverage report:
+ <span class='pc_cov'>67%</span>
+ </h1>
+ <img id='keyboard_icon' src='keybd_closed.png'>
+ </div>
+</div>
+
+<div class='help_panel'>
+ <img id='panel_icon' src='keybd_open.png'>
+ <p class='legend'>Hot-keys on this page</p>
+ <div>
+ <p class='keyhelp'>
+ <span class='key'>n</span>
+ <span class='key'>s</span>
+ <span class='key'>m</span>
+ <span class='key'>x</span>
+
+ <span class='key'>c</span> &nbsp; change column sorting
+ </p>
+ </div>
+</div>
+
+<div id='index'>
+ <table class='index'>
+ <thead>
+
+ <tr class='tablehead' title='Click to sort'>
+ <th class='name left headerSortDown shortkey_n'>Module</th>
+ <th class='shortkey_s'>statements</th>
+ <th class='shortkey_m'>missing</th>
+ <th class='shortkey_x'>excluded</th>
+
+ <th class='right shortkey_c'>coverage</th>
+ </tr>
+ </thead>
+
+ <tfoot>
+ <tr class='total'>
+ <td class='name left'>Total</td>
+ <td>3</td>
+ <td>1</td>
+ <td>0</td>
+
+ <td class='right'>67%</td>
+ </tr>
+ </tfoot>
+ <tbody>
+
+ <tr class='file'>
+ <td class='name left'><a href='a.html'>a</a></td>
+ <td>3</td>
+ <td>1</td>
+ <td>0</td>
+
+ <td class='right'>67%</td>
+ </tr>
+
+ </tbody>
+ </table>
+</div>
+
+<div id='footer'>
+ <div class='content'>
+ <p>
+ <a class='nav' href='http://nedbatchelder.com/code/coverage/3.5a1'>coverage.py v3.5a1</a>
+ </p>
+ </div>
+</div>
+
+</body>
+</html>
diff --git a/test/farm/html/gold_styled/style.css b/test/farm/html/gold_styled/style.css
new file mode 100644
index 00000000..c40357b8
--- /dev/null
+++ b/test/farm/html/gold_styled/style.css
@@ -0,0 +1,275 @@
+/* CSS styles for Coverage. */
+/* Page-wide styles */
+html, body, h1, h2, h3, p, td, th {
+ margin: 0;
+ padding: 0;
+ border: 0;
+ outline: 0;
+ font-weight: inherit;
+ font-style: inherit;
+ font-size: 100%;
+ font-family: inherit;
+ vertical-align: baseline;
+ }
+
+/* Set baseline grid to 16 pt. */
+body {
+ font-family: georgia, serif;
+ font-size: 1em;
+ }
+
+html>body {
+ font-size: 16px;
+ }
+
+/* Set base font size to 12/16 */
+p {
+ font-size: .75em; /* 12/16 */
+ line-height: 1.3333em; /* 16/12 */
+ }
+
+table {
+ border-collapse: collapse;
+ }
+
+a.nav {
+ text-decoration: none;
+ color: inherit;
+ }
+a.nav:hover {
+ text-decoration: underline;
+ color: inherit;
+ }
+
+/* Page structure */
+#header {
+ background: #f8f8f8;
+ width: 100%;
+ border-bottom: 1px solid #eee;
+ }
+
+#source {
+ padding: 1em;
+ font-family: "courier new", monospace;
+ }
+
+#indexfile #footer {
+ margin: 1em 3em;
+ }
+
+#pyfile #footer {
+ margin: 1em 1em;
+ }
+
+#footer .content {
+ padding: 0;
+ font-size: 85%;
+ font-family: verdana, sans-serif;
+ color: #666666;
+ font-style: italic;
+ }
+
+#index {
+ margin: 1em 0 0 3em;
+ }
+
+/* Header styles */
+#header .content {
+ padding: 1em 3em;
+ }
+
+h1 {
+ font-size: 1.25em;
+}
+
+h2.stats {
+ margin-top: .5em;
+ font-size: 1em;
+}
+.stats span {
+ border: 1px solid;
+ padding: .1em .25em;
+ margin: 0 .1em;
+ cursor: pointer;
+ border-color: #999 #ccc #ccc #999;
+}
+.stats span.hide_run, .stats span.hide_exc,
+.stats span.hide_mis, .stats span.hide_par,
+.stats span.par.hide_run.hide_par {
+ border-color: #ccc #999 #999 #ccc;
+}
+.stats span.par.hide_run {
+ border-color: #999 #ccc #ccc #999;
+}
+
+/* Help panel */
+#keyboard_icon {
+ float: right;
+ cursor: pointer;
+}
+
+.help_panel {
+ position: absolute;
+ background: #ffc;
+ padding: .5em;
+ border: 1px solid #883;
+ display: none;
+}
+
+#indexfile .help_panel {
+ width: 20em; height: 4em;
+}
+
+#pyfile .help_panel {
+ width: 16em; height: 8em;
+}
+
+.help_panel .legend {
+ font-style: italic;
+ margin-bottom: 1em;
+}
+
+#panel_icon {
+ float: right;
+ cursor: pointer;
+}
+
+.keyhelp {
+ margin: .75em;
+}
+
+.keyhelp .key {
+ border: 1px solid black;
+ border-color: #888 #333 #333 #888;
+ padding: .1em .35em;
+ font-family: monospace;
+ font-weight: bold;
+ background: #eee;
+}
+
+/* Source file styles */
+.linenos p {
+ text-align: right;
+ margin: 0;
+ padding: 0 .5em;
+ color: #999999;
+ font-family: verdana, sans-serif;
+ font-size: .625em; /* 10/16 */
+ line-height: 1.6em; /* 16/10 */
+ }
+.linenos p.highlight {
+ background: #ffdd00;
+ }
+.linenos p a {
+ text-decoration: none;
+ color: #999999;
+ }
+.linenos p a:hover {
+ text-decoration: underline;
+ color: #999999;
+ }
+
+td.text {
+ width: 100%;
+ }
+.text p {
+ margin: 0;
+ padding: 0 0 0 .5em;
+ border-left: 2px solid #ffffff;
+ white-space: nowrap;
+ }
+
+.text p.mis {
+ background: #ffdddd;
+ border-left: 2px solid #ff0000;
+ }
+.text p.run, .text p.run.hide_par {
+ background: #ddffdd;
+ border-left: 2px solid #00ff00;
+ }
+.text p.exc {
+ background: #eeeeee;
+ border-left: 2px solid #808080;
+ }
+.text p.par, .text p.par.hide_run {
+ background: #ffffaa;
+ border-left: 2px solid #eeee99;
+ }
+.text p.hide_run, .text p.hide_exc, .text p.hide_mis, .text p.hide_par,
+.text p.hide_run.hide_par {
+ background: inherit;
+ }
+
+.text span.annotate {
+ font-family: georgia;
+ font-style: italic;
+ color: #666;
+ float: right;
+ padding-right: .5em;
+ }
+.text p.hide_par span.annotate {
+ display: none;
+ }
+
+/* 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;
+ width: 5em;
+ padding: .25em .5em;
+ border-bottom: 1px solid #eee;
+ }
+#index th {
+ font-style: italic;
+ color: #333;
+ border-bottom: 1px solid #ccc;
+ cursor: pointer;
+ }
+#index th:hover {
+ background: #eee;
+ border-bottom: 1px solid #999;
+ }
+#index td.left, #index th.left {
+ padding-left: 0;
+ }
+#index td.right, #index th.right {
+ padding-right: 0;
+ }
+#index th.headerSortDown, #index th.headerSortUp {
+ border-bottom: 1px solid #000;
+ }
+#index td.name, #index th.name {
+ text-align: left;
+ width: auto;
+ }
+#index td.name a {
+ text-decoration: none;
+ color: #000;
+ }
+#index td.name a:hover {
+ text-decoration: underline;
+ color: #000;
+ }
+#index tr.total {
+ }
+#index tr.total td {
+ font-weight: bold;
+ border-top: 1px solid #ccc;
+ border-bottom: none;
+ }
+#index tr.file:hover {
+ background: #eeeeee;
+ }
diff --git a/test/farm/html/run_styled.py b/test/farm/html/run_styled.py
new file mode 100644
index 00000000..3a212957
--- /dev/null
+++ b/test/farm/html/run_styled.py
@@ -0,0 +1,28 @@
+def html_it():
+ """Run coverage and make an HTML report for a."""
+ import coverage
+ cov = coverage.coverage()
+ cov.start()
+ import a
+ cov.stop()
+ cov.html_report(a, directory="../html_styled", extra_css="extra.css")
+
+runfunc(html_it, rundir="src")
+
+# HTML files will change often. Check that the sizes are reasonable,
+# and check that certain key strings are in the output.
+compare("gold_styled", "html_styled", size_within=10, file_pattern="*.html")
+compare("gold_styled", "html_styled", size_within=10, file_pattern="*.css")
+contains("html_styled/a.html",
+ "<link rel='stylesheet' href='extra.css' type='text/css'>",
+ "<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_styled/index.html",
+ "<link rel='stylesheet' href='extra.css' type='text/css'>",
+ "<a href='a.html'>a</a>",
+ "<span class='pc_cov'>67%</span>"
+ )
+
+clean("html_styled")
diff --git a/test/farm/html/src/extra.css b/test/farm/html/src/extra.css
new file mode 100644
index 00000000..46c41fcd
--- /dev/null
+++ b/test/farm/html/src/extra.css
@@ -0,0 +1 @@
+/* Doesn't matter what goes in here, it gets copied. */
diff --git a/test/test_config.py b/test/test_config.py
index 236216f6..bf42bf76 100644
--- a/test/test_config.py
+++ b/test/test_config.py
@@ -138,7 +138,7 @@ class ConfigFileTest(CoverageTest):
[html]
directory = c:\\tricky\\dir.somewhere
-
+ extra_css=something/extra.css
[xml]
output=mycov.xml
@@ -176,6 +176,7 @@ class ConfigFileTest(CoverageTest):
)
self.assertTrue(cov.config.show_missing)
self.assertEqual(cov.config.html_dir, r"c:\tricky\dir.somewhere")
+ self.assertEqual(cov.config.extra_css, "something/extra.css")
self.assertEqual(cov.config.xml_output, "mycov.xml")