|
37 | 37 | from scancode import FileOptionType |
38 | 38 | from scancode import OUTPUT_GROUP |
39 | 39 |
|
| 40 | +# Python 2 and 3 support |
| 41 | +try: |
| 42 | + # Python 2 |
| 43 | + unicode |
| 44 | + str = unicode # NOQA |
| 45 | +except NameError: |
| 46 | + # Python 3 |
| 47 | + unicode = str # NOQA |
| 48 | + basestring = str # NOQA |
| 49 | + |
| 50 | + |
| 51 | +# Tracing flags |
| 52 | +TRACE = True |
| 53 | + |
| 54 | + |
| 55 | +def logger_debug(*args): |
| 56 | + pass |
| 57 | + |
| 58 | + |
| 59 | +if TRACE: |
| 60 | + import sys |
| 61 | + import logging |
| 62 | + |
| 63 | + logger = logging.getLogger(__name__) |
| 64 | + logging.basicConfig(stream=sys.stdout) |
| 65 | + logger.setLevel(logging.DEBUG) |
| 66 | + |
| 67 | + def logger_debug(*args): |
| 68 | + return logger.debug(' '.join(isinstance(a, unicode) |
| 69 | + and a or repr(a) for a in args)) |
| 70 | + |
40 | 71 |
|
41 | 72 | @output_impl |
42 | 73 | class CsvOutput(OutputPlugin): |
@@ -199,114 +230,98 @@ def collect_keys(mapping, key_group): |
199 | 230 | yield flat |
200 | 231 |
|
201 | 232 |
|
202 | | -def flatten_package(_package, path, prefix='package__'): |
| 233 | +def get_package_columns(_columns=set()): |
| 234 | + """ |
| 235 | + Return (and cache in_columns) a set of package column names included in the |
| 236 | + CSV output. |
| 237 | + Some columsn are excluded for now such as lists of mappings: these do not |
| 238 | + serialize well to CSV |
| 239 | + """ |
| 240 | + if _columns: |
| 241 | + return _columns |
| 242 | + |
| 243 | + from packagedcode.models import Package |
203 | 244 |
|
204 | 245 | # exclude some columns for now that contain list of items |
205 | | - excluded_package_columns = { |
| 246 | + excluded_columns = { |
206 | 247 | # list of strings |
207 | | - 'download_checksums', |
208 | 248 | 'keywords', |
209 | 249 | # list of dicts |
210 | 250 | 'parties', |
211 | 251 | 'dependencies', |
| 252 | + 'source_packages', |
| 253 | + } |
212 | 254 |
|
213 | | - # comming from a match |
| 255 | + # some extra columns for components |
| 256 | + extra_columns = [ |
| 257 | + 'components', |
| 258 | + 'owner_name', |
| 259 | + 'reference_notes', |
| 260 | + 'description', |
| 261 | + 'notice_filename', |
| 262 | + 'notice_url', |
| 263 | + ] |
214 | 264 |
|
215 | | - # we have license_expression we do not need more |
216 | | - 'licenses_summary', |
217 | | - 'licenses', |
218 | | - 'license_choices_expression', |
219 | | - 'license_choices', |
| 265 | + fields = Package.fields() + extra_columns |
| 266 | + _columns = set(f for f in fields if f not in excluded_columns) |
| 267 | + return _columns |
220 | 268 |
|
221 | | - 'api_url', |
222 | | - 'uuid', |
223 | | - 'sha1', |
224 | | - 'md5', |
225 | | - 'owner', |
226 | 269 |
|
227 | | - 'reference_notes', |
228 | | - 'project', |
229 | | - 'codescan_identifier', |
230 | | - 'is_license_notice', |
231 | | - 'is_copyright_notice', |
232 | | - 'is_notice_in_codebase', |
233 | | - # 'notice_filename', |
234 | | - # 'notice_url', |
235 | | - 'website_terms_of_use', |
236 | | - 'is_active', |
237 | | - 'curation_level', |
238 | | - 'completion_level', |
239 | | - 'guidance', |
240 | | - 'admin_notes', |
241 | | - 'ip_sensitivity_approved', |
242 | | - 'affiliate_obligations', |
243 | | - 'affiliate_obligation_triggers', |
244 | | - 'concluded_license', |
245 | | - 'legal_comments', |
246 | | - 'legal_reviewed', |
247 | | - 'approval_reference', |
248 | | - 'distribution_formats_allowed', |
249 | | - 'acceptable_linkage', |
250 | | - 'export_restrictions', |
251 | | - 'approved_download_location', |
252 | | - 'approved_community_interaction', |
253 | | - 'urn', |
254 | | - 'created_date', |
255 | | - 'last_modified_date', |
256 | | - 'dataspace', |
257 | | - 'external_references', |
258 | | - 'display_name', |
259 | | - 'notes', |
260 | | - 'origin_date', |
261 | | - 'sublicense_allowed', |
262 | | - } |
| 270 | +def flatten_package(_package, path, prefix='package__'): |
| 271 | + |
| 272 | + # known package columns |
| 273 | + |
| 274 | + package_columns = get_package_columns() |
263 | 275 |
|
264 | 276 | pack = OrderedDict(Resource=path) |
265 | 277 | for k, val in _package.items(): |
266 | | - # FIXME: we only keep for now some of the value collections |
267 | | - if k in excluded_package_columns: |
| 278 | + if k not in package_columns: |
268 | 279 | continue |
| 280 | + |
269 | 281 | # prefix columns with "package__" |
270 | 282 | nk = prefix + k |
271 | 283 |
|
272 | 284 | if k == 'version': |
273 | | - if val: |
| 285 | + val = val or '' |
| 286 | + if val and not val.lower().startswith('v'): |
274 | 287 | # prefix versions with a v to avoid spreadsheet tools to mistake |
275 | | - # a version for a number or date. |
| 288 | + # a version for a number or date when reading CSVs (common with |
| 289 | + # Excel and LibreOffice). |
276 | 290 | val = 'v ' + val |
277 | | - pack[nk] = val |
278 | | - else: |
279 | | - pack[nk] = '' |
| 291 | + pack[nk] = val |
280 | 292 | continue |
281 | 293 |
|
282 | | - # these may come from a match |
283 | | - |
| 294 | + # these may come from a component matched |
284 | 295 | if k == 'components' and val and isinstance(val, list): |
285 | | - for compo in val: |
286 | | - for compo_key, compo_val in compo.items(): |
287 | | - if compo_key in excluded_package_columns: |
| 296 | + for component in val: |
| 297 | + for component_key, component_val in component.items(): |
| 298 | + if component_key not in package_columns: |
288 | 299 | continue |
289 | 300 |
|
290 | | - compo_nk = nk + '__' + compo_key |
| 301 | + component_new_key = nk + '__' + component_key |
291 | 302 |
|
292 | | - if compo_val is None: |
293 | | - pack[compo_nk] = '' |
| 303 | + if component_val is None: |
| 304 | + pack[component_new_key] = '' |
294 | 305 | continue |
295 | 306 |
|
296 | | - if not isinstance(compo_val, basestring): |
297 | | - compo_val = repr(compo_val) |
298 | | - existing = pack.get(compo_nk) or [] |
299 | | - pack[compo_nk] = ' \n'.join(existing + [compo_val]) |
300 | | - continue |
| 307 | + if isinstance(component_val, list): |
| 308 | + component_val = '\n'.join(component_val) |
| 309 | + |
| 310 | + if not isinstance(component_val, str): |
| 311 | + component_val = repr(component_val) |
| 312 | + |
| 313 | + existing = pack.get(component_new_key) or [] |
| 314 | + if not isinstance(existing, list): |
| 315 | + existing = [existing] |
| 316 | + |
| 317 | + if TRACE: |
| 318 | + logger_debug('component_new_key:', component_new_key, 'existing:', type(existing), repr(existing)) |
| 319 | + logger_debug('component_key:', component_key, 'component_val:', type(component_val), repr(component_val)) |
301 | 320 |
|
302 | | - if k == 'match': |
303 | | - for mk, mval in val.items(): |
304 | | - match_nk = nk + '__' + mk |
305 | | - pack[match_nk] = mval |
| 321 | + pack[component_new_key] = ' \n'.join(existing + [component_val]) |
306 | 322 | continue |
307 | 323 |
|
308 | 324 | # everything else |
309 | | - # collect all the keys |
310 | 325 |
|
311 | 326 | pack[nk] = '' |
312 | 327 |
|
|
0 commit comments