diff options
-rw-r--r-- | CHANGES.txt | 3 | ||||
-rw-r--r-- | coverage/config.py | 3 | ||||
-rw-r--r-- | coverage/control.py | 11 | ||||
-rw-r--r-- | coverage/html.py | 15 | ||||
-rw-r--r-- | coverage/htmlfiles/index.html | 3 | ||||
-rw-r--r-- | coverage/htmlfiles/pyfile.html | 3 | ||||
-rw-r--r-- | doc/config.rst | 4 | ||||
-rw-r--r-- | test/farm/html/gold_styled/a.html | 95 | ||||
-rw-r--r-- | test/farm/html/gold_styled/extra.css | 1 | ||||
-rw-r--r-- | test/farm/html/gold_styled/index.html | 89 | ||||
-rw-r--r-- | test/farm/html/gold_styled/style.css | 275 | ||||
-rw-r--r-- | test/farm/html/run_styled.py | 28 | ||||
-rw-r--r-- | test/farm/html/src/extra.css | 1 | ||||
-rw-r--r-- | test/test_config.py | 3 |
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> toggle line displays + </p> +<p class='keyhelp'> + <span class='key'>j</span> + <span class='key'>k</span> next/prev highlighted chunk + </p> +<p class='keyhelp'> + <span class='key'>0</span> (zero) top of page + </p> +<p class='keyhelp'> + <span class='key'>1</span> (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'> </span></p> +<p id='t2' class='pln'><span class='strut'> </span></p> +<p id='t3' class='stm run hide_run'><span class='key'>if</span> <span class='num'>1</span> <span class='op'><</span> <span class='num'>2</span><span class='op'>:</span><span class='strut'> </span></p> +<p id='t4' class='pln'> <span class='com'># Needed a < to look at HTML entities.</span><span class='strut'> </span></p> +<p id='t5' class='stm run hide_run'> <span class='nam'>a</span> <span class='op'>=</span> <span class='num'>3</span><span class='strut'> </span></p> +<p id='t6' class='pln'><span class='key'>else</span><span class='op'>:</span><span class='strut'> </span></p> +<p id='t7' class='stm mis'> <span class='nam'>a</span> <span class='op'>=</span> <span class='num'>4</span><span class='strut'> </span></p> + + </td> + </tr> + </table> +</div> + +<div id='footer'> + <div class='content'> + <p> + <a class='nav' href='index.html'>« index</a> <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> 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'><</span> <span class='num'>2</span>", + " <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") |