@@ -157,15 +157,78 @@ def trim_version_report(version_report, reduced_path_set):
157
157
reduced_aggregate_bom_view_entries = [e for e in aggregate_bom_view_entries if f"{ e ['producerProject' ]['id' ]} :{ e ['producerReleases' ][0 ]['id' ]} " in deduplicated ]
158
158
version_report ['aggregateBomViewEntries' ] = reduced_aggregate_bom_view_entries
159
159
160
- def write_output_file (version_report , output_file ):
160
+ '''
161
+
162
+ CSV output details
163
+
164
+ component name = aggregateBomViewEntries[].producerProject.name
165
+ version name = aggregateBomViewEntries[].producerReleases[0].version
166
+ license = licenses[].licenseDisplay
167
+ file path = extract from detailedFileBomViewEntries
168
+ match type = aggregateBomViewEntries[].matchTypes
169
+ review status = aggregateBomViewEntries[].reviewSummary.reviewStatus
170
+
171
+ '''
172
+ def get_csv_fieldnames ():
173
+ return ['component name' , 'version name' , 'license' , 'match type' , 'review status' ]
174
+
175
+ def get_csv_data (version_report , keep_dupes ):
176
+ csv_data = list ()
177
+ components = list ()
178
+ for bom_view_entry in version_report ['aggregateBomViewEntries' ]:
179
+ entry = dict ()
180
+ entry ['component name' ] = bom_view_entry ['producerProject' ]['name' ]
181
+ entry ['version name' ] = bom_view_entry ['producerReleases' ][0 ]['version' ]
182
+ entry ['license' ] = bom_view_entry ['licenses' ][0 ]['licenseDisplay' ].replace (' AND ' ,';' ).replace ('(' ,'' ).replace (')' ,'' )
183
+ pid = bom_view_entry ['producerProject' ]['id' ]
184
+ vid = bom_view_entry ['producerReleases' ][0 ]['id' ]
185
+ #path_list = [p['path'] for p in version_report['detailedFileBomViewEntries'] if p['projectId'] == pid and p['versionId'] == vid]
186
+ #entry['file path'] = ';'.join(path_list)
187
+ entry ['match type' ] = ';' .join (bom_view_entry ['matchTypes' ])
188
+ entry ['review status' ] = bom_view_entry ['reviewSummary' ]['reviewStatus' ]
189
+
190
+ # Only add if this component was not previously added.
191
+ composite_key = pid + vid
192
+ if composite_key not in components :
193
+ csv_data .append (entry )
194
+ components .append (composite_key )
195
+ if keep_dupes :
196
+ return csv_data
197
+ else :
198
+ return remove_duplicates (csv_data )
199
+
200
+ def remove_duplicates (data ):
201
+ # Put data into buckets by version
202
+ buckets = dict ()
203
+ for row in data :
204
+ name = row ['component name' ].lower ()
205
+ version = row ['version name' ]
206
+ if not version in buckets :
207
+ buckets [version ] = [row ]
208
+ else :
209
+ buckets [version ].append (row )
210
+ # Run reduction process for component names that start with existing component name
211
+ # This process will ignore case in component names
212
+ for set in buckets .values ():
213
+ set .sort (key = lambda d : d ['component name' ].lower ())
214
+ for row in set :
215
+ index = set .index (row )
216
+ name = row ['component name' ].lower ()
217
+ while index + 1 < len (set ) and set [index + 1 ]['component name' ].lower ().startswith (name ):
218
+ set .pop (index + 1 )
219
+ reduced_data = list ()
220
+ for b in buckets .values ():
221
+ reduced_data .extend (b )
222
+ return reduced_data
223
+
224
+ def write_output_file (version_report , output_file , keep_dupes ):
161
225
if output_file .lower ().endswith (".csv" ):
162
226
logging .info (f"Writing CSV output into { output_file } " )
163
- field_names = list ( version_report [ 'aggregateBomViewEntries' ][ 0 ]. keys () )
227
+ field_names = get_csv_fieldnames ( )
164
228
with open (output_file , "w" ) as f :
165
- writer = csv .DictWriter (f , fieldnames = field_names , extrasaction = 'ignore' ) # TODO
229
+ writer = csv .DictWriter (f , fieldnames = field_names , extrasaction = 'ignore' , quoting = csv . QUOTE_ALL ) # TODO
166
230
writer .writeheader ()
167
- writer .writerows (version_report ['aggregateBomViewEntries' ])
168
-
231
+ writer .writerows (get_csv_data (version_report , keep_dupes ))
169
232
return
170
233
# If it's neither, then .json
171
234
if not output_file .lower ().endswith (".json" ):
@@ -183,6 +246,7 @@ def parse_command_args():
183
246
parser .add_argument ("-pn" , "--project-name" , required = True , help = "Project Name" )
184
247
parser .add_argument ("-pv" , "--project-version-name" , required = True , help = "Project Version Name" )
185
248
parser .add_argument ("-o" , "--output-file" , required = False , help = "File name to write output. File extension determines format .json and .csv, json is the default." )
249
+ parser .add_argument ("-kd" , "--keep-dupes" , action = 'store_true' , help = "Do not reduce CVS data by fuzzy matching component names" )
186
250
parser .add_argument ("-kh" , "--keep_hierarchy" , action = 'store_true' , help = "Set to keep all entries in the sources report. Will not remove components found under others." )
187
251
parser .add_argument ("--report-retries" , metavar = "" , type = int , default = RETRY_LIMIT , help = "Retries for receiving the generated BlackDuck report. Generating copyright report tends to take longer minutes." )
188
252
parser .add_argument ("--report-timeout" , metavar = "" , type = int , default = RETRY_TIMER , help = "Wait time between subsequent download attempts." )
@@ -230,7 +294,7 @@ def main():
230
294
trim_version_report (version_report , reduced_path_set )
231
295
logging .info (f"Truncated dataset contains { len (version_report ['aggregateBomViewEntries' ])} bom entries and { len (version_report ['detailedFileBomViewEntries' ])} file view entries" )
232
296
233
- write_output_file (version_report , output_file )
297
+ write_output_file (version_report , output_file , args . keep_dupes )
234
298
235
299
# Combine component data with selected file data
236
300
# Output result with CSV anf JSON as options.
0 commit comments