eric7/DebugClients/Python/coverage/html.py

branch
eric7
changeset 9099
0e511e0e94a3
parent 8991
2fc945191992
equal deleted inserted replaced
9098:fb9351497cea 9099:0e511e0e94a3
120 ) 120 )
121 121
122 return file_data 122 return file_data
123 123
124 124
125 class FileToReport:
126 """A file we're considering reporting."""
127 def __init__(self, fr, analysis):
128 self.fr = fr
129 self.analysis = analysis
130 self.rootname = flat_rootname(fr.relative_filename())
131 self.html_filename = self.rootname + ".html"
132
133
125 class HtmlReporter: 134 class HtmlReporter:
126 """HTML reporting.""" 135 """HTML reporting."""
127 136
128 # These files will be copied from the htmlfiles directory to the output 137 # These files will be copied from the htmlfiles directory to the output
129 # directory. 138 # directory.
163 self.all_files_nums = [] 172 self.all_files_nums = []
164 self.incr = IncrementalChecker(self.directory) 173 self.incr = IncrementalChecker(self.directory)
165 self.datagen = HtmlDataGeneration(self.coverage) 174 self.datagen = HtmlDataGeneration(self.coverage)
166 self.totals = Numbers(precision=self.config.precision) 175 self.totals = Numbers(precision=self.config.precision)
167 self.directory_was_empty = False 176 self.directory_was_empty = False
177 self.first_fr = None
178 self.final_fr = None
168 179
169 self.template_globals = { 180 self.template_globals = {
170 # Functions available in the templates. 181 # Functions available in the templates.
171 'escape': escape, 182 'escape': escape,
172 'pair': pair, 183 'pair': pair,
186 'category': { 197 'category': {
187 'exc': 'exc show_exc', 198 'exc': 'exc show_exc',
188 'mis': 'mis show_mis', 199 'mis': 'mis show_mis',
189 'par': 'par run show_par', 200 'par': 'par run show_par',
190 'run': 'run', 201 'run': 'run',
191 } 202 },
192 } 203 }
193 self.pyfile_html_source = read_data("pyfile.html") 204 self.pyfile_html_source = read_data("pyfile.html")
194 self.source_tmpl = Templite(self.pyfile_html_source, self.template_globals) 205 self.source_tmpl = Templite(self.pyfile_html_source, self.template_globals)
195 206
196 def report(self, morfs): 207 def report(self, morfs):
202 # Read the status data and check that this run used the same 213 # Read the status data and check that this run used the same
203 # global data as the last run. 214 # global data as the last run.
204 self.incr.read() 215 self.incr.read()
205 self.incr.check_global_data(self.config, self.pyfile_html_source) 216 self.incr.check_global_data(self.config, self.pyfile_html_source)
206 217
207 # Process all the files. 218 # Process all the files. For each page we need to supply a link
219 # to the next and previous page.
220 files_to_report = []
221
208 for fr, analysis in get_analysis_to_report(self.coverage, morfs): 222 for fr, analysis in get_analysis_to_report(self.coverage, morfs):
209 self.html_file(fr, analysis) 223 ftr = FileToReport(fr, analysis)
224 should = self.should_report_file(ftr)
225 if should:
226 files_to_report.append(ftr)
227 else:
228 file_be_gone(os.path.join(self.directory, ftr.html_filename))
229
230 for i, ftr in enumerate(files_to_report):
231 if i == 0:
232 prev_html = "index.html"
233 else:
234 prev_html = files_to_report[i - 1].html_filename
235 if i == len(files_to_report) - 1:
236 next_html = "index.html"
237 else:
238 next_html = files_to_report[i + 1].html_filename
239 self.write_html_file(ftr, prev_html, next_html)
210 240
211 if not self.all_files_nums: 241 if not self.all_files_nums:
212 raise NoDataError("No data to report.") 242 raise NoDataError("No data to report.")
213 243
214 self.totals = sum(self.all_files_nums) 244 self.totals = sum(self.all_files_nums)
215 245
216 # Write the index file. 246 # Write the index file.
217 self.index_file() 247 if files_to_report:
248 first_html = files_to_report[0].html_filename
249 final_html = files_to_report[-1].html_filename
250 else:
251 first_html = final_html = "index.html"
252 self.index_file(first_html, final_html)
218 253
219 self.make_local_static_report_files() 254 self.make_local_static_report_files()
220 return self.totals.n_statements and self.totals.pc_covered 255 return self.totals.n_statements and self.totals.pc_covered
256
257 def make_directory(self):
258 """Make sure our htmlcov directory exists."""
259 ensure_dir(self.directory)
260 if not os.listdir(self.directory):
261 self.directory_was_empty = True
221 262
222 def make_local_static_report_files(self): 263 def make_local_static_report_files(self):
223 """Make local instances of static files for HTML report.""" 264 """Make local instances of static files for HTML report."""
224 # The files we provide must always be copied. 265 # The files we provide must always be copied.
225 for static in self.STATIC_FILES: 266 for static in self.STATIC_FILES:
234 275
235 # The user may have extra CSS they want copied. 276 # The user may have extra CSS they want copied.
236 if self.extra_css: 277 if self.extra_css:
237 shutil.copyfile(self.config.extra_css, os.path.join(self.directory, self.extra_css)) 278 shutil.copyfile(self.config.extra_css, os.path.join(self.directory, self.extra_css))
238 279
239 def html_file(self, fr, analysis): 280 def should_report_file(self, ftr):
240 """Generate an HTML file for one source file.""" 281 """Determine if we'll report this file."""
241 rootname = flat_rootname(fr.relative_filename())
242 html_filename = rootname + ".html"
243 ensure_dir(self.directory)
244 if not os.listdir(self.directory):
245 self.directory_was_empty = True
246 html_path = os.path.join(self.directory, html_filename)
247
248 # Get the numbers for this file. 282 # Get the numbers for this file.
249 nums = analysis.numbers 283 nums = ftr.analysis.numbers
250 self.all_files_nums.append(nums) 284 self.all_files_nums.append(nums)
251 285
252 if self.skip_covered: 286 if self.skip_covered:
253 # Don't report on 100% files. 287 # Don't report on 100% files.
254 no_missing_lines = (nums.n_missing == 0) 288 no_missing_lines = (nums.n_missing == 0)
255 no_missing_branches = (nums.n_partial_branches == 0) 289 no_missing_branches = (nums.n_partial_branches == 0)
256 if no_missing_lines and no_missing_branches: 290 if no_missing_lines and no_missing_branches:
257 # If there's an existing file, remove it. 291 # If there's an existing file, remove it.
258 file_be_gone(html_path)
259 self.skipped_covered_count += 1 292 self.skipped_covered_count += 1
260 return 293 return False
261 294
262 if self.skip_empty: 295 if self.skip_empty:
263 # Don't report on empty files. 296 # Don't report on empty files.
264 if nums.n_statements == 0: 297 if nums.n_statements == 0:
265 file_be_gone(html_path)
266 self.skipped_empty_count += 1 298 self.skipped_empty_count += 1
267 return 299 return False
300
301 return True
302
303 def write_html_file(self, ftr, prev_html, next_html):
304 """Generate an HTML file for one source file."""
305 self.make_directory()
268 306
269 # Find out if the file on disk is already correct. 307 # Find out if the file on disk is already correct.
270 if self.incr.can_skip_file(self.data, fr, rootname): 308 if self.incr.can_skip_file(self.data, ftr.fr, ftr.rootname):
271 self.file_summaries.append(self.incr.index_info(rootname)) 309 self.file_summaries.append(self.incr.index_info(ftr.rootname))
272 return 310 return
273 311
274 # Write the HTML page for this file. 312 # Write the HTML page for this file.
275 file_data = self.datagen.data_for_file(fr, analysis) 313 file_data = self.datagen.data_for_file(ftr.fr, ftr.analysis)
276 for ldata in file_data.lines: 314 for ldata in file_data.lines:
277 # Build the HTML for the line. 315 # Build the HTML for the line.
278 html = [] 316 html = []
279 for tok_type, tok_text in ldata.tokens: 317 for tok_type, tok_text in ldata.tokens:
280 if tok_type == "ws": 318 if tok_type == "ws":
290 # 202F is NARROW NO-BREAK SPACE. 328 # 202F is NARROW NO-BREAK SPACE.
291 # 219B is RIGHTWARDS ARROW WITH STROKE. 329 # 219B is RIGHTWARDS ARROW WITH STROKE.
292 ldata.annotate = ",   ".join( 330 ldata.annotate = ",   ".join(
293 f"{ldata.number} ↛ {d}" 331 f"{ldata.number} ↛ {d}"
294 for d in ldata.short_annotations 332 for d in ldata.short_annotations
295 ) 333 )
296 else: 334 else:
297 ldata.annotate = None 335 ldata.annotate = None
298 336
299 if ldata.long_annotations: 337 if ldata.long_annotations:
300 longs = ldata.long_annotations 338 longs = ldata.long_annotations
304 ldata.annotate_long = "{:d} missed branches: {}".format( 342 ldata.annotate_long = "{:d} missed branches: {}".format(
305 len(longs), 343 len(longs),
306 ", ".join( 344 ", ".join(
307 f"{num:d}) {ann_long}" 345 f"{num:d}) {ann_long}"
308 for num, ann_long in enumerate(longs, start=1) 346 for num, ann_long in enumerate(longs, start=1)
309 ), 347 ),
310 ) 348 )
311 else: 349 else:
312 ldata.annotate_long = None 350 ldata.annotate_long = None
313 351
314 css_classes = [] 352 css_classes = []
315 if ldata.category: 353 if ldata.category:
316 css_classes.append(self.template_globals['category'][ldata.category]) 354 css_classes.append(self.template_globals['category'][ldata.category])
317 ldata.css_class = ' '.join(css_classes) or "pln" 355 ldata.css_class = ' '.join(css_classes) or "pln"
318 356
319 html = self.source_tmpl.render(file_data.__dict__) 357 html_path = os.path.join(self.directory, ftr.html_filename)
358 html = self.source_tmpl.render({
359 **file_data.__dict__,
360 'prev_html': prev_html,
361 'next_html': next_html,
362 })
320 write_html(html_path, html) 363 write_html(html_path, html)
321 364
322 # Save this file's information for the index file. 365 # Save this file's information for the index file.
323 index_info = { 366 index_info = {
324 'nums': nums, 367 'nums': ftr.analysis.numbers,
325 'html_filename': html_filename, 368 'html_filename': ftr.html_filename,
326 'relative_filename': fr.relative_filename(), 369 'relative_filename': ftr.fr.relative_filename(),
327 } 370 }
328 self.file_summaries.append(index_info) 371 self.file_summaries.append(index_info)
329 self.incr.set_index_info(rootname, index_info) 372 self.incr.set_index_info(ftr.rootname, index_info)
330 373
331 def index_file(self): 374 def index_file(self, first_html, final_html):
332 """Write the index.html file for this report.""" 375 """Write the index.html file for this report."""
376 self.make_directory()
333 index_tmpl = Templite(read_data("index.html"), self.template_globals) 377 index_tmpl = Templite(read_data("index.html"), self.template_globals)
334 378
335 skipped_covered_msg = skipped_empty_msg = "" 379 skipped_covered_msg = skipped_empty_msg = ""
336 if self.skipped_covered_count: 380 if self.skipped_covered_count:
337 n = self.skipped_covered_count 381 n = self.skipped_covered_count
343 html = index_tmpl.render({ 387 html = index_tmpl.render({
344 'files': self.file_summaries, 388 'files': self.file_summaries,
345 'totals': self.totals, 389 'totals': self.totals,
346 'skipped_covered_msg': skipped_covered_msg, 390 'skipped_covered_msg': skipped_covered_msg,
347 'skipped_empty_msg': skipped_empty_msg, 391 'skipped_empty_msg': skipped_empty_msg,
392 'first_html': first_html,
393 'final_html': final_html,
348 }) 394 })
349 395
350 index_file = os.path.join(self.directory, "index.html") 396 index_file = os.path.join(self.directory, "index.html")
351 write_html(index_file, html) 397 write_html(index_file, html)
352 self.coverage._message(f"Wrote HTML report to {index_file}") 398 self.coverage._message(f"Wrote HTML report to {index_file}")

eric ide

mercurial