OLD | NEW |
1 """HTML reporting for Coverage.""" | 1 """HTML reporting for Coverage.""" |
2 | 2 |
3 import os, re, shutil, sys | 3 import os, re, shutil, sys |
4 | 4 |
5 import coverage | 5 import coverage |
6 from coverage.backward import pickle | 6 from coverage.backward import pickle |
7 from coverage.misc import CoverageException, Hasher | 7 from coverage.misc import CoverageException, Hasher |
8 from coverage.phystokens import source_token_lines, source_encoding | 8 from coverage.phystokens import source_token_lines, source_encoding |
9 from coverage.report import Reporter | 9 from coverage.report import Reporter |
10 from coverage.results import Numbers | 10 from coverage.results import Numbers |
11 from coverage.templite import Templite | 11 from coverage.templite import Templite |
12 | 12 |
13 # Disable pylint msg W0612, because a bunch of variables look unused, but | |
14 # they're accessed in a Templite context via locals(). | |
15 # pylint: disable=W0612 | |
16 | 13 |
17 def data_filename(fname): | 14 # Static files are looked for in a list of places. |
18 """Return the path to a data file of ours.""" | 15 STATIC_PATH = [ |
19 return os.path.join(os.path.split(__file__)[0], fname) | 16 # The place Debian puts system Javascript libraries. |
| 17 "/usr/share/javascript", |
| 18 |
| 19 # Our htmlfiles directory. |
| 20 os.path.join(os.path.dirname(__file__), "htmlfiles"), |
| 21 ] |
| 22 |
| 23 def data_filename(fname, pkgdir=""): |
| 24 """Return the path to a data file of ours. |
| 25 |
| 26 The file is searched for on `STATIC_PATH`, and the first place it's found, |
| 27 is returned. |
| 28 |
| 29 Each directory in `STATIC_PATH` is searched as-is, and also, if `pkgdir` |
| 30 is provided, at that subdirectory. |
| 31 |
| 32 """ |
| 33 for static_dir in STATIC_PATH: |
| 34 static_filename = os.path.join(static_dir, fname) |
| 35 if os.path.exists(static_filename): |
| 36 return static_filename |
| 37 if pkgdir: |
| 38 static_filename = os.path.join(static_dir, pkgdir, fname) |
| 39 if os.path.exists(static_filename): |
| 40 return static_filename |
| 41 raise CoverageException("Couldn't find static file %r" % fname) |
| 42 |
20 | 43 |
21 def data(fname): | 44 def data(fname): |
22 """Return the contents of a data file of ours.""" | 45 """Return the contents of a data file of ours.""" |
23 data_file = open(data_filename(fname)) | 46 data_file = open(data_filename(fname)) |
24 try: | 47 try: |
25 return data_file.read() | 48 return data_file.read() |
26 finally: | 49 finally: |
27 data_file.close() | 50 data_file.close() |
28 | 51 |
29 | 52 |
30 class HtmlReporter(Reporter): | 53 class HtmlReporter(Reporter): |
31 """HTML reporting.""" | 54 """HTML reporting.""" |
32 | 55 |
33 # These files will be copied from the htmlfiles dir to the output dir. | 56 # These files will be copied from the htmlfiles dir to the output dir. |
34 STATIC_FILES = [ | 57 STATIC_FILES = [ |
35 "style.css", | 58 ("style.css", ""), |
36 "jquery-1.4.3.min.js", | 59 ("jquery.min.js", "jquery"), |
37 "jquery.hotkeys.js", | 60 ("jquery.hotkeys.js", "jquery-hotkeys"), |
38 "jquery.isonscreen.js", | 61 ("jquery.isonscreen.js", "jquery-isonscreen"), |
39 "jquery.tablesorter.min.js", | 62 ("jquery.tablesorter.min.js", "jquery-tablesorter"), |
40 "coverage_html.js", | 63 ("coverage_html.js", ""), |
41 "keybd_closed.png", | 64 ("keybd_closed.png", ""), |
42 "keybd_open.png", | 65 ("keybd_open.png", ""), |
43 ] | 66 ] |
44 | 67 |
45 def __init__(self, cov, config): | 68 def __init__(self, cov, config): |
46 super(HtmlReporter, self).__init__(cov, config) | 69 super(HtmlReporter, self).__init__(cov, config) |
47 self.directory = None | 70 self.directory = None |
48 self.template_globals = { | 71 self.template_globals = { |
49 'escape': escape, | 72 'escape': escape, |
50 'title': self.config.html_title, | 73 'title': self.config.html_title, |
51 '__url__': coverage.__url__, | 74 '__url__': coverage.__url__, |
52 '__version__': coverage.__version__, | 75 '__version__': coverage.__version__, |
53 } | 76 } |
54 self.source_tmpl = Templite( | 77 self.source_tmpl = Templite( |
55 data("htmlfiles/pyfile.html"), self.template_globals | 78 data("pyfile.html"), self.template_globals |
56 ) | 79 ) |
57 | 80 |
58 self.coverage = cov | 81 self.coverage = cov |
59 | 82 |
60 self.files = [] | 83 self.files = [] |
61 self.arcs = self.coverage.data.has_arcs() | 84 self.arcs = self.coverage.data.has_arcs() |
62 self.status = HtmlStatus() | 85 self.status = HtmlStatus() |
63 self.extra_css = None | 86 self.extra_css = None |
64 self.totals = Numbers() | 87 self.totals = Numbers() |
65 | 88 |
(...skipping 29 matching lines...) Expand all Loading... |
95 # Write the index file. | 118 # Write the index file. |
96 self.index_file() | 119 self.index_file() |
97 | 120 |
98 self.make_local_static_report_files() | 121 self.make_local_static_report_files() |
99 | 122 |
100 return self.totals.pc_covered | 123 return self.totals.pc_covered |
101 | 124 |
102 def make_local_static_report_files(self): | 125 def make_local_static_report_files(self): |
103 """Make local instances of static files for HTML report.""" | 126 """Make local instances of static files for HTML report.""" |
104 # The files we provide must always be copied. | 127 # The files we provide must always be copied. |
105 for static in self.STATIC_FILES: | 128 for static, pkgdir in self.STATIC_FILES: |
106 shutil.copyfile( | 129 shutil.copyfile( |
107 data_filename("htmlfiles/" + static), | 130 data_filename(static, pkgdir), |
108 os.path.join(self.directory, static) | 131 os.path.join(self.directory, static) |
109 ) | 132 ) |
110 | 133 |
111 # The user may have extra CSS they want copied. | 134 # The user may have extra CSS they want copied. |
112 if self.extra_css: | 135 if self.extra_css: |
113 shutil.copyfile( | 136 shutil.copyfile( |
114 self.config.extra_css, | 137 self.config.extra_css, |
115 os.path.join(self.directory, self.extra_css) | 138 os.path.join(self.directory, self.extra_css) |
116 ) | 139 ) |
117 | 140 |
(...skipping 36 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
154 if sys.version_info < (3, 0): | 177 if sys.version_info < (3, 0): |
155 encoding = source_encoding(source) | 178 encoding = source_encoding(source) |
156 # Some UTF8 files have the dreaded UTF8 BOM. If so, junk it. | 179 # Some UTF8 files have the dreaded UTF8 BOM. If so, junk it. |
157 if encoding.startswith("utf-8") and source[:3] == "\xef\xbb\xbf": | 180 if encoding.startswith("utf-8") and source[:3] == "\xef\xbb\xbf": |
158 source = source[3:] | 181 source = source[3:] |
159 encoding = "utf-8" | 182 encoding = "utf-8" |
160 | 183 |
161 # Get the numbers for this file. | 184 # Get the numbers for this file. |
162 nums = analysis.numbers | 185 nums = analysis.numbers |
163 | 186 |
164 missing_branch_arcs = analysis.missing_branch_arcs() | 187 if self.arcs: |
165 arcs = self.arcs | 188 missing_branch_arcs = analysis.missing_branch_arcs() |
166 | 189 |
167 # These classes determine which lines are highlighted by default. | 190 # These classes determine which lines are highlighted by default. |
168 c_run = "run hide_run" | 191 c_run = "run hide_run" |
169 c_exc = "exc" | 192 c_exc = "exc" |
170 c_mis = "mis" | 193 c_mis = "mis" |
171 c_par = "par " + c_run | 194 c_par = "par " + c_run |
172 | 195 |
173 lines = [] | 196 lines = [] |
174 | 197 |
175 for lineno, line in enumerate(source_token_lines(source)): | 198 for lineno, line in enumerate(source_token_lines(source)): |
(...skipping 37 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
213 | 236 |
214 lines.append({ | 237 lines.append({ |
215 'html': ''.join(html), | 238 'html': ''.join(html), |
216 'number': lineno, | 239 'number': lineno, |
217 'class': ' '.join(line_class) or "pln", | 240 'class': ' '.join(line_class) or "pln", |
218 'annotate': annotate_html, | 241 'annotate': annotate_html, |
219 'annotate_title': annotate_title, | 242 'annotate_title': annotate_title, |
220 }) | 243 }) |
221 | 244 |
222 # Write the HTML page for this file. | 245 # Write the HTML page for this file. |
| 246 html = spaceless(self.source_tmpl.render({ |
| 247 'c_exc': c_exc, 'c_mis': c_mis, 'c_par': c_par, 'c_run': c_run, |
| 248 'arcs': self.arcs, 'extra_css': self.extra_css, |
| 249 'cu': cu, 'nums': nums, 'lines': lines, |
| 250 })) |
| 251 |
| 252 if sys.version_info < (3, 0): |
| 253 html = html.decode(encoding) |
| 254 |
223 html_filename = flat_rootname + ".html" | 255 html_filename = flat_rootname + ".html" |
224 html_path = os.path.join(self.directory, html_filename) | 256 html_path = os.path.join(self.directory, html_filename) |
225 extra_css = self.extra_css | |
226 | |
227 html = spaceless(self.source_tmpl.render(locals())) | |
228 if sys.version_info < (3, 0): | |
229 html = html.decode(encoding) | |
230 self.write_html(html_path, html) | 257 self.write_html(html_path, html) |
231 | 258 |
232 # Save this file's information for the index file. | 259 # Save this file's information for the index file. |
233 index_info = { | 260 index_info = { |
234 'nums': nums, | 261 'nums': nums, |
235 'html_filename': html_filename, | 262 'html_filename': html_filename, |
236 'name': cu.name, | 263 'name': cu.name, |
237 } | 264 } |
238 self.files.append(index_info) | 265 self.files.append(index_info) |
239 self.status.set_index_info(flat_rootname, index_info) | 266 self.status.set_index_info(flat_rootname, index_info) |
240 | 267 |
241 def index_file(self): | 268 def index_file(self): |
242 """Write the index.html file for this report.""" | 269 """Write the index.html file for this report.""" |
243 index_tmpl = Templite( | 270 index_tmpl = Templite( |
244 data("htmlfiles/index.html"), self.template_globals | 271 data("index.html"), self.template_globals |
245 ) | 272 ) |
246 | 273 |
247 files = self.files | 274 self.totals = sum([f['nums'] for f in self.files]) |
248 arcs = self.arcs | |
249 | 275 |
250 self.totals = totals = sum([f['nums'] for f in files]) | 276 html = index_tmpl.render({ |
251 extra_css = self.extra_css | 277 'arcs': self.arcs, |
| 278 'extra_css': self.extra_css, |
| 279 'files': self.files, |
| 280 'totals': self.totals, |
| 281 }) |
252 | 282 |
253 html = index_tmpl.render(locals()) | |
254 if sys.version_info < (3, 0): | 283 if sys.version_info < (3, 0): |
255 html = html.decode("utf-8") | 284 html = html.decode("utf-8") |
256 self.write_html( | 285 self.write_html( |
257 os.path.join(self.directory, "index.html"), | 286 os.path.join(self.directory, "index.html"), |
258 html | 287 html |
259 ) | 288 ) |
260 | 289 |
261 # Write the latest hashes for next time. | 290 # Write the latest hashes for next time. |
262 self.status.write(self.directory) | 291 self.status.write(self.directory) |
263 | 292 |
(...skipping 94 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
358 | 387 |
359 def spaceless(html): | 388 def spaceless(html): |
360 """Squeeze out some annoying extra space from an HTML string. | 389 """Squeeze out some annoying extra space from an HTML string. |
361 | 390 |
362 Nicely-formatted templates mean lots of extra space in the result. | 391 Nicely-formatted templates mean lots of extra space in the result. |
363 Get rid of some. | 392 Get rid of some. |
364 | 393 |
365 """ | 394 """ |
366 html = re.sub(r">\s+<p ", ">\n<p ", html) | 395 html = re.sub(r">\s+<p ", ">\n<p ", html) |
367 return html | 396 return html |
OLD | NEW |