-
-
Notifications
You must be signed in to change notification settings - Fork 433
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Support report terminal output in Markdown Table format #1418 #1479
Changes from 12 commits
e050395
3712391
b222476
286dd66
8746883
b41de9b
b3179db
3f3c6bc
488d886
28e690f
20edcfa
ceef236
3017cb1
52c8ae8
9f2c978
bb00948
1d43a83
d3cf237
87113b4
3d366a1
c45e764
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6,7 +6,7 @@ | |
import sys | ||
|
||
from coverage.exceptions import ConfigError, NoDataError | ||
from coverage.misc import human_sorted_items | ||
from coverage.misc import human_key | ||
from coverage.report import get_analysis_to_report | ||
from coverage.results import Numbers | ||
|
||
|
@@ -30,6 +30,112 @@ def writeout(self, line): | |
self.outfile.write(line.rstrip()) | ||
self.outfile.write("\n") | ||
|
||
def _report_text(self, header, lines_values, total_line, end_lines): | ||
"internal method to print report data in text format" | ||
# Prepare the formatting strings, header, and column sorting. | ||
max_name = max([len(fr.relative_filename()) for (fr, analysis) in \ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You don't need a backslash to continue a line if you are in unbalanced brackets (as you are here). Also, I prefer to have the closing bracket on a line of its own. Take a look through other code in the repo to get a sense of the style. |
||
self.fr_analysis] + [5]) + 2 | ||
n = self.config.precision | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It seems odd to me to be looking at |
||
max_n = max(n+6, 7) | ||
h_form = dict( | ||
Name="{:{name_len}}", Stmts="{:>7}", Miss="{:>7}", | ||
Branch="{:>7}", BrPart="{:>7}", Cover="{:>{n}}", | ||
Missing="{:>9}") | ||
header_items = [ | ||
h_form[item].format(item, name_len=max_name, n=max_n) | ||
for item in header] | ||
header_str = "".join(header_items) | ||
rule = "-" * len(header_str) | ||
|
||
# Write the header | ||
self.writeout(header_str) | ||
self.writeout(rule) | ||
|
||
column_order = dict(name=0, stmts=1, miss=2, cover=-1) | ||
if self.branches: | ||
column_order.update(dict(branch=3, brpart=4)) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
|
||
h_form.update(dict(Cover="{:>{n}}%"), Missing=" {:9}") | ||
for values in lines_values: | ||
# build string with line values | ||
line_items = [ | ||
h_form[item].format(str(value), | ||
name_len=max_name, n=max_n-1) for item, value in zip(header, values)] | ||
text = "".join(line_items) | ||
self.writeout(text) | ||
|
||
# Write a TOTAL line | ||
if total_line: | ||
self.writeout(rule) | ||
line_items = [ | ||
h_form[item].format(str(value), | ||
name_len=max_name, n=max_n-1) for item, value in zip(header, total_line)] | ||
text = "".join(line_items) | ||
self.writeout(text) | ||
|
||
for end_line in end_lines: | ||
self.writeout(end_line) | ||
return self.total.n_statements and self.total.pc_covered | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You don't need to return this from here, or from |
||
|
||
def _report_markdown(self, header, lines_values, total_line, end_lines): | ||
"internal method to print report data in markdown format" | ||
# Prepare the formatting strings, header, and column sorting. | ||
max_name = max([len(fr.relative_filename().replace("_","\\_")) for\ | ||
(fr, analysis) in self.fr_analysis] + [9]) + 1 | ||
h_form = dict( | ||
Name="| {:{name_len}}|", Stmts="{:>7} |", Miss="{:>7} |", | ||
Branch="{:>7} |", BrPart="{:>7} |", Cover="{:>{n}} |", | ||
Missing="{:>9} |") | ||
n = self.config.precision | ||
max_n = max(n+6, 7) + 4 | ||
header_items = [ | ||
h_form[item].format(item, name_len=max_name, n=max_n) for item in header] | ||
header_str = "".join(header_items) | ||
rule_str = "|" + " ".join(["- |".rjust(len(header_items[0])-1, '-')] + | ||
["-: |".rjust(len(item)-1, '-') for item in header_items[1:]]) | ||
|
||
# Write the header | ||
self.writeout(header_str) | ||
self.writeout(rule_str) | ||
|
||
column_order = dict(name=0, stmts=1, miss=2, cover=-1) | ||
if self.branches: | ||
column_order.update(dict(branch=3, brpart=4)) | ||
|
||
for values in lines_values: | ||
# build string with line values | ||
h_form.update(dict(Cover="{:>{n}}% |")) | ||
line_items = [ | ||
h_form[item].format(str(value).replace("_", "\\_"), | ||
name_len=max_name, n=max_n-1) for item, value in zip(header, values)] | ||
text = "".join(line_items) | ||
self.writeout(text) | ||
|
||
# Write the TOTAL line | ||
if total_line: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I ran a coverage report locally, and this line (and the similar one in There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The total line is always printed, if there is coverage data to report. The |
||
total_form = dict( | ||
Name="| {:>{name_len}}** |", Stmts="{:>5}** |", Miss="{:>5}** |", | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't understand why the trailing stars are in the format string, but the leading stars are in the values on line 131? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It was just to less code. If I inserted the stars like |
||
Branch="{:>5}** |", BrPart="{:>5}** |", Cover="{:>{n}}%** |", | ||
Missing="{:>9} |") | ||
total_line_items = [] | ||
for item, value in zip(header, total_line): | ||
if item == "Missing": | ||
if value == '': | ||
insert = value | ||
else: | ||
insert = "**" + value + "**" | ||
total_line_items += total_form[item].format(\ | ||
insert, name_len=max_name-3) | ||
else: | ||
total_line_items += total_form[item].format(\ | ||
"**"+str(value), name_len=max_name-3, n=max_n-3) | ||
total_row_str = "".join(total_line_items) | ||
self.writeout(total_row_str) | ||
for end_line in end_lines: | ||
self.writeout(end_line) | ||
return self.total.n_statements and self.total.pc_covered | ||
|
||
|
||
def report(self, morfs, outfile=None): | ||
"""Writes a report summarizing coverage statistics per module. | ||
|
||
|
@@ -44,36 +150,19 @@ def report(self, morfs, outfile=None): | |
self.report_one_file(fr, analysis) | ||
|
||
# Prepare the formatting strings, header, and column sorting. | ||
max_name = max([len(fr.relative_filename()) for (fr, analysis) in self.fr_analysis] + [5]) | ||
fmt_name = "%%- %ds " % max_name | ||
fmt_skip_covered = "\n%s file%s skipped due to complete coverage." | ||
fmt_skip_empty = "\n%s empty file%s skipped." | ||
|
||
header = (fmt_name % "Name") + " Stmts Miss" | ||
fmt_coverage = fmt_name + "%6d %6d" | ||
header = ("Name", "Stmts", "Miss",) | ||
if self.branches: | ||
header += " Branch BrPart" | ||
fmt_coverage += " %6d %6d" | ||
width100 = Numbers(precision=self.config.precision).pc_str_width() | ||
header += "%*s" % (width100+4, "Cover") | ||
fmt_coverage += "%%%ds%%%%" % (width100+3,) | ||
header += ("Branch", "BrPart",) | ||
header += ("Cover",) | ||
if self.config.show_missing: | ||
header += " Missing" | ||
fmt_coverage += " %s" | ||
rule = "-" * len(header) | ||
header += ("Missing",) | ||
|
||
column_order = dict(name=0, stmts=1, miss=2, cover=-1) | ||
if self.branches: | ||
column_order.update(dict(branch=3, brpart=4)) | ||
|
||
# Write the header | ||
self.writeout(header) | ||
self.writeout(rule) | ||
|
||
# `lines` is a list of pairs, (line text, line values). The line text | ||
# is a string that will be printed, and line values is a tuple of | ||
# sortable values. | ||
lines = [] | ||
# `lines_values` is list of tuples of sortable values. | ||
lines_values = [] | ||
|
||
for (fr, analysis) in self.fr_analysis: | ||
nums = analysis.numbers | ||
|
@@ -84,54 +173,58 @@ def report(self, morfs, outfile=None): | |
args += (nums.pc_covered_str,) | ||
if self.config.show_missing: | ||
args += (analysis.missing_formatted(branches=True),) | ||
text = fmt_coverage % args | ||
# Add numeric percent coverage so that sorting makes sense. | ||
args += (nums.pc_covered,) | ||
lines.append((text, args)) | ||
lines_values.append(args) | ||
|
||
# Sort the lines and write them out. | ||
# line-sorting. | ||
sort_option = (self.config.sort or "name").lower() | ||
reverse = False | ||
if sort_option[0] == '-': | ||
reverse = True | ||
sort_option = sort_option[1:] | ||
elif sort_option[0] == '+': | ||
sort_option = sort_option[1:] | ||
|
||
sort_idx = column_order.get(sort_option) | ||
if sort_idx is None: | ||
raise ConfigError(f"Invalid sorting option: {self.config.sort!r}") | ||
if sort_option == "name": | ||
lines = human_sorted_items(lines, reverse=reverse) | ||
lines_values.sort(key=lambda tup: (human_key(tup[0]), tup[1]), | ||
reverse=reverse) | ||
else: | ||
position = column_order.get(sort_option) | ||
if position is None: | ||
raise ConfigError(f"Invalid sorting option: {self.config.sort!r}") | ||
lines.sort(key=lambda l: (l[1][position], l[0]), reverse=reverse) | ||
lines_values.sort(key=lambda tup: (tup[sort_idx], tup[0]), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I use a line-length of 100, so no need to wrap this line, and probably others in this file. |
||
reverse=reverse) | ||
|
||
for line in lines: | ||
self.writeout(line[0]) | ||
|
||
# Write a TOTAL line if we had at least one file. | ||
# calculate total if we had at least one file. | ||
total_line = () | ||
if self.total.n_files > 0: | ||
self.writeout(rule) | ||
args = ("TOTAL", self.total.n_statements, self.total.n_missing) | ||
total_line = ("TOTAL", self.total.n_statements, self.total.n_missing) | ||
if self.branches: | ||
args += (self.total.n_branches, self.total.n_partial_branches) | ||
args += (self.total.pc_covered_str,) | ||
total_line += (self.total.n_branches, self.total.n_partial_branches) | ||
total_line += (self.total.pc_covered_str,) | ||
if self.config.show_missing: | ||
args += ("",) | ||
self.writeout(fmt_coverage % args) | ||
total_line += ("",) | ||
|
||
# Write other final lines. | ||
# create other final lines | ||
end_lines = [] | ||
if not self.total.n_files and not self.skipped_count: | ||
raise NoDataError("No data to report.") | ||
|
||
if self.config.skip_covered and self.skipped_count: | ||
self.writeout( | ||
fmt_skip_covered % (self.skipped_count, 's' if self.skipped_count > 1 else '') | ||
) | ||
file_suffix = 's' if self.skipped_count>1 else '' | ||
fmt_skip_covered = f"\n{self.skipped_count} file{file_suffix} "\ | ||
"skipped due to complete coverage." | ||
end_lines.append(fmt_skip_covered) | ||
if self.config.skip_empty and self.empty_count: | ||
self.writeout( | ||
fmt_skip_empty % (self.empty_count, 's' if self.empty_count > 1 else '') | ||
) | ||
file_suffix = 's' if self.empty_count>1 else '' | ||
fmt_skip_empty = \ | ||
f"\n{self.empty_count} empty file{file_suffix} skipped." | ||
end_lines.append(fmt_skip_empty) | ||
|
||
text_format = self.config.output_format or 'text' | ||
if text_format.lower() == 'markdown': | ||
self._report_markdown(header, lines_values, total_line, end_lines) | ||
else: | ||
self._report_text(header, lines_values, total_line, end_lines) | ||
|
||
return self.total.n_statements and self.total.pc_covered | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Docstrings should always use triple-quote style, and complete English grammar.