6
6
import sys
7
7
8
8
from coverage .exceptions import ConfigError , NoDataError
9
- from coverage .misc import human_sorted_items
9
+ from coverage .misc import human_key
10
10
from coverage .report import get_analysis_to_report
11
11
from coverage .results import Numbers
12
12
@@ -30,6 +30,119 @@ def writeout(self, line):
30
30
self .outfile .write (line .rstrip ())
31
31
self .outfile .write ("\n " )
32
32
33
+ def _report_text (self , header , lines_values , total_line , end_lines ):
34
+ """Internal method that prints report data in text format.
35
+
36
+ `header` is a tuple with captions.
37
+ `lines_values` is list of tuples of sortable values.
38
+ `total_line` is a tuple with values of the total line.
39
+ `end_lines` is a tuple of ending lines with information about skipped files.
40
+
41
+ """
42
+ # Prepare the formatting strings, header, and column sorting.
43
+ max_name = max ([len (line [0 ]) for line in lines_values ] + [5 ]) + 1
44
+ max_n = max (len (total_line [header .index ("Cover" )]) + 2 , len (" Cover" )) + 1
45
+ max_n = max ([max_n ] + [len (line [header .index ("Cover" )]) + 2 for line in lines_values ])
46
+ h_form = dict (
47
+ Name = "{:{name_len}}" ,
48
+ Stmts = "{:>7}" ,
49
+ Miss = "{:>7}" ,
50
+ Branch = "{:>7}" ,
51
+ BrPart = "{:>7}" ,
52
+ Cover = "{:>{n}}" ,
53
+ Missing = "{:>10}" ,
54
+ )
55
+ header_items = [
56
+ h_form [item ].format (item , name_len = max_name , n = max_n )
57
+ for item in header
58
+ ]
59
+ header_str = "" .join (header_items )
60
+ rule = "-" * len (header_str )
61
+
62
+ # Write the header
63
+ self .writeout (header_str )
64
+ self .writeout (rule )
65
+
66
+ h_form .update (dict (Cover = "{:>{n}}%" ), Missing = " {:9}" )
67
+ for values in lines_values :
68
+ # build string with line values
69
+ line_items = [
70
+ h_form [item ].format (str (value ),
71
+ name_len = max_name , n = max_n - 1 ) for item , value in zip (header , values )
72
+ ]
73
+ text = "" .join (line_items )
74
+ self .writeout (text )
75
+
76
+ # Write a TOTAL line
77
+ self .writeout (rule )
78
+ line_items = [
79
+ h_form [item ].format (str (value ),
80
+ name_len = max_name , n = max_n - 1 ) for item , value in zip (header , total_line )
81
+ ]
82
+ text = "" .join (line_items )
83
+ self .writeout (text )
84
+
85
+ for end_line in end_lines :
86
+ self .writeout (end_line )
87
+
88
+ def _report_markdown (self , header , lines_values , total_line , end_lines ):
89
+ """Internal method that prints report data in markdown format.
90
+
91
+ `header` is a tuple with captions.
92
+ `lines_values` is a sorted list of tuples containing coverage information.
93
+ `total_line` is a tuple with values of the total line.
94
+ `end_lines` is a tuple of ending lines with information about skipped files.
95
+
96
+ """
97
+ # Prepare the formatting strings, header, and column sorting.
98
+ max_name = max ([len (line [0 ].replace ("_" , "\\ _" )) for line in lines_values ] + [9 ])
99
+ max_name += 1
100
+ h_form = dict (
101
+ Name = "| {:{name_len}}|" ,
102
+ Stmts = "{:>9} |" ,
103
+ Miss = "{:>9} |" ,
104
+ Branch = "{:>9} |" ,
105
+ BrPart = "{:>9} |" ,
106
+ Cover = "{:>{n}} |" ,
107
+ Missing = "{:>10} |" ,
108
+ )
109
+ max_n = max (len (total_line [header .index ("Cover" )]) + 6 , len (" Cover " ))
110
+ header_items = [h_form [item ].format (item , name_len = max_name , n = max_n ) for item in header ]
111
+ header_str = "" .join (header_items )
112
+ rule_str = "|" + " " .join (["- |" .rjust (len (header_items [0 ])- 1 , '-' )] +
113
+ ["-: |" .rjust (len (item )- 1 , '-' ) for item in header_items [1 :]]
114
+ )
115
+
116
+ # Write the header
117
+ self .writeout (header_str )
118
+ self .writeout (rule_str )
119
+
120
+ for values in lines_values :
121
+ # build string with line values
122
+ h_form .update (dict (Cover = "{:>{n}}% |" ))
123
+ line_items = [
124
+ h_form [item ].format (str (value ).replace ("_" , "\\ _" ),
125
+ name_len = max_name , n = max_n - 1 ) for item , value in zip (header , values )
126
+ ]
127
+ text = "" .join (line_items )
128
+ self .writeout (text )
129
+
130
+ # Write the TOTAL line
131
+ h_form .update (dict (Name = "|{:>{name_len}} |" , Cover = "{:>{n}} |" ))
132
+ total_line_items = []
133
+ for item , value in zip (header , total_line ):
134
+ if value == '' :
135
+ insert = value
136
+ elif item == "Cover" :
137
+ insert = f" **{ value } %**"
138
+ else :
139
+ insert = f" **{ value } **"
140
+ total_line_items += h_form [item ].format (insert , name_len = max_name , n = max_n )
141
+ total_row_str = "" .join (total_line_items )
142
+ self .writeout (total_row_str )
143
+ for end_line in end_lines :
144
+ self .writeout (end_line )
145
+
33
146
def report (self , morfs , outfile = None ):
34
147
"""Writes a report summarizing coverage statistics per module.
35
148
@@ -44,36 +157,19 @@ def report(self, morfs, outfile=None):
44
157
self .report_one_file (fr , analysis )
45
158
46
159
# Prepare the formatting strings, header, and column sorting.
47
- max_name = max ([len (fr .relative_filename ()) for (fr , analysis ) in self .fr_analysis ] + [5 ])
48
- fmt_name = "%%- %ds " % max_name
49
- fmt_skip_covered = "\n %s file%s skipped due to complete coverage."
50
- fmt_skip_empty = "\n %s empty file%s skipped."
51
-
52
- header = (fmt_name % "Name" ) + " Stmts Miss"
53
- fmt_coverage = fmt_name + "%6d %6d"
160
+ header = ("Name" , "Stmts" , "Miss" ,)
54
161
if self .branches :
55
- header += " Branch BrPart"
56
- fmt_coverage += " %6d %6d"
57
- width100 = Numbers (precision = self .config .precision ).pc_str_width ()
58
- header += "%*s" % (width100 + 4 , "Cover" )
59
- fmt_coverage += "%%%ds%%%%" % (width100 + 3 ,)
162
+ header += ("Branch" , "BrPart" ,)
163
+ header += ("Cover" ,)
60
164
if self .config .show_missing :
61
- header += " Missing"
62
- fmt_coverage += " %s"
63
- rule = "-" * len (header )
165
+ header += ("Missing" ,)
64
166
65
167
column_order = dict (name = 0 , stmts = 1 , miss = 2 , cover = - 1 )
66
168
if self .branches :
67
169
column_order .update (dict (branch = 3 , brpart = 4 ))
68
170
69
- # Write the header
70
- self .writeout (header )
71
- self .writeout (rule )
72
-
73
- # `lines` is a list of pairs, (line text, line values). The line text
74
- # is a string that will be printed, and line values is a tuple of
75
- # sortable values.
76
- lines = []
171
+ # `lines_values` is list of tuples of sortable values.
172
+ lines_values = []
77
173
78
174
for (fr , analysis ) in self .fr_analysis :
79
175
nums = analysis .numbers
@@ -84,54 +180,55 @@ def report(self, morfs, outfile=None):
84
180
args += (nums .pc_covered_str ,)
85
181
if self .config .show_missing :
86
182
args += (analysis .missing_formatted (branches = True ),)
87
- text = fmt_coverage % args
88
- # Add numeric percent coverage so that sorting makes sense.
89
183
args += (nums .pc_covered ,)
90
- lines .append (( text , args ) )
184
+ lines_values .append (args )
91
185
92
- # Sort the lines and write them out .
186
+ # line-sorting .
93
187
sort_option = (self .config .sort or "name" ).lower ()
94
188
reverse = False
95
189
if sort_option [0 ] == '-' :
96
190
reverse = True
97
191
sort_option = sort_option [1 :]
98
192
elif sort_option [0 ] == '+' :
99
193
sort_option = sort_option [1 :]
100
-
194
+ sort_idx = column_order .get (sort_option )
195
+ if sort_idx is None :
196
+ raise ConfigError (f"Invalid sorting option: { self .config .sort !r} " )
101
197
if sort_option == "name" :
102
- lines = human_sorted_items ( lines , reverse = reverse )
198
+ lines_values . sort ( key = lambda tup : ( human_key ( tup [ 0 ]), tup [ 1 ]) , reverse = reverse )
103
199
else :
104
- position = column_order .get (sort_option )
105
- if position is None :
106
- raise ConfigError (f"Invalid sorting option: { self .config .sort !r} " )
107
- lines .sort (key = lambda l : (l [1 ][position ], l [0 ]), reverse = reverse )
108
-
109
- for line in lines :
110
- self .writeout (line [0 ])
111
-
112
- # Write a TOTAL line if we had at least one file.
113
- if self .total .n_files > 0 :
114
- self .writeout (rule )
115
- args = ("TOTAL" , self .total .n_statements , self .total .n_missing )
116
- if self .branches :
117
- args += (self .total .n_branches , self .total .n_partial_branches )
118
- args += (self .total .pc_covered_str ,)
119
- if self .config .show_missing :
120
- args += ("" ,)
121
- self .writeout (fmt_coverage % args )
200
+ lines_values .sort (key = lambda tup : (tup [sort_idx ], tup [0 ]), reverse = reverse )
201
+
202
+ # calculate total if we had at least one file.
203
+ total_line = ("TOTAL" , self .total .n_statements , self .total .n_missing )
204
+ if self .branches :
205
+ total_line += (self .total .n_branches , self .total .n_partial_branches )
206
+ total_line += (self .total .pc_covered_str ,)
207
+ if self .config .show_missing :
208
+ total_line += ("" ,)
122
209
123
- # Write other final lines.
210
+ # create other final lines
211
+ end_lines = []
124
212
if not self .total .n_files and not self .skipped_count :
125
213
raise NoDataError ("No data to report." )
126
214
127
215
if self .config .skip_covered and self .skipped_count :
128
- self .writeout (
129
- fmt_skip_covered % (self .skipped_count , 's' if self .skipped_count > 1 else '' )
216
+ file_suffix = 's' if self .skipped_count > 1 else ''
217
+ fmt_skip_covered = (
218
+ f"\n { self .skipped_count } file{ file_suffix } skipped due to complete coverage."
130
219
)
220
+ end_lines .append (fmt_skip_covered )
131
221
if self .config .skip_empty and self .empty_count :
132
- self .writeout (
133
- fmt_skip_empty % (self .empty_count , 's' if self .empty_count > 1 else '' )
134
- )
222
+ file_suffix = 's' if self .empty_count > 1 else ''
223
+ fmt_skip_empty = f"\n { self .empty_count } empty file{ file_suffix } skipped."
224
+ end_lines .append (fmt_skip_empty )
225
+
226
+ text_format = self .config .output_format or "text"
227
+ if text_format == "markdown" :
228
+ formatter = self ._report_markdown
229
+ else :
230
+ formatter = self ._report_text
231
+ formatter (header , lines_values , total_line , end_lines )
135
232
136
233
return self .total .n_statements and self .total .pc_covered
137
234
0 commit comments