3434import os
3535import posixpath
3636from posixpath import dirname
37+
38+ import yaml
3739import re
3840import sys
3941
4042if sys .version_info [0 ] < 3 : # Python 2
4143 import backports .csv as csv
44+ from itertools import izip_longest as zip_longest
4245 from urlparse import urljoin , urlparse
4346 from urllib2 import urlopen , Request , HTTPError
4447else : # Python 3
4548 basestring = str
4649 import csv
50+ from itertools import zip_longest
4751 from urllib .parse import urljoin , urlparse
4852 from urllib .request import urlopen , Request
4953 from urllib .error import HTTPError
6064from attributecode import util
6165from attributecode .util import add_unc
6266from attributecode .util import copy_license_notice_files
63- from attributecode .util import check_duplicate_keys_about_file
6467from attributecode .util import on_windows
6568from attributecode .util import UNC_PREFIX
6669from attributecode .util import UNC_PREFIX_POSIX
@@ -107,11 +110,6 @@ def validate(self, *args, **kwargs):
107110 msg = u'Field %(name)s is required'
108111 errors .append (Error (CRITICAL , msg % locals ()))
109112 return errors
110- """else:
111- # no error for not present non required fields
112- # FIXME: should we add an info?
113- # CY: I don't think so.
114- pass"""
115113 else :
116114 # present fields should have content ...
117115 if not self .has_content :
@@ -986,51 +984,34 @@ def load(self, location, use_mapping=False, mapping_file=None):
986984 loc = add_unc (loc )
987985 with codecs .open (loc , encoding = 'utf-8' ) as txt :
988986 input_text = txt .read ()
987+ # Check for duplicated key
988+ yaml .load (input_text , Loader = util .NoDuplicateLoader )
989+ '''
989990 dup_keys = check_duplicate_keys_about_file(input_text)
990991 if dup_keys:
991992 msg = ('Duplicated key name(s): %(dup_keys)s' % locals())
992993 errors.append(Error(ERROR, msg % locals()))
993994 else:
994- """
995- The running_inventory defines if the current process is 'inventory' or not.
996- This is used for the validation of the about_resource_path.
997- In the 'inventory' command, the code will use the parent of the about_file_path
998- location and join with the 'about_resource_path' for the validation.
999- On the other hand, in the 'gen' command, the code will use the
1000- generated location (aka base_dir) along with the parent of the about_file_path
1001- and then join with the 'about_resource_path'
1002- """
1003- running_inventory = True
1004- errs = self .load_dict (saneyaml .load (input_text ), base_dir , running_inventory , use_mapping , mapping_file )
1005- errors .extend (errs )
995+ '''
996+ """
997+ The running_inventory defines if the current process is 'inventory' or not.
998+ This is used for the validation of the about_resource_path.
999+ In the 'inventory' command, the code will use the parent of the about_file_path
1000+ location and join with the 'about_resource_path' for the validation.
1001+ On the other hand, in the 'gen' command, the code will use the
1002+ generated location (aka base_dir) along with the parent of the about_file_path
1003+ and then join with the 'about_resource_path'
1004+ """
1005+ running_inventory = True
1006+ errs = self .load_dict (saneyaml .load (input_text ), base_dir , running_inventory , use_mapping , mapping_file )
1007+ errors .extend (errs )
10061008 except Exception as e :
10071009 msg = 'Cannot load invalid ABOUT file: %(location)r: %(e)r\n ' + str (e )
10081010 errors .append (Error (CRITICAL , msg % locals ()))
10091011
10101012 self .errors = errors
10111013 return errors
10121014
1013- def loads (self , string , base_dir ):
1014- """
1015- Load the ABOUT file from string. Return a list of errors.
1016- """
1017- lines = string .splitlines (True )
1018- errors = self .load_lines (lines , base_dir )
1019- self .errors = errors
1020- return errors
1021-
1022- def load_lines (self , lines , base_dir ):
1023- """
1024- Load the ABOUT file from a lines list. Return a list of errors.
1025- """
1026- errors = []
1027- parse_errors , fields = parse (lines )
1028- errors .extend (parse_errors )
1029- process_errors = self .process (fields , base_dir )
1030- errors .extend (process_errors )
1031- self .errors = errors
1032- return errors
1033-
10341015 def load_dict (self , fields_dict , base_dir , running_inventory = False ,
10351016 use_mapping = False , mapping_file = None ,
10361017 license_notice_text_location = None , with_empty = True ):
@@ -1050,17 +1031,52 @@ def load_dict(self, fields_dict, base_dir, running_inventory=False,
10501031 self .errors = errors
10511032 return errors
10521033
1034+
10531035 def dumps (self , with_absent = False , with_empty = True ):
10541036 """
10551037 Return self as a formatted ABOUT string.
10561038 If with_absent, include absent (not present) fields.
10571039 If with_empty, include empty fields.
10581040 """
1059- serialized = []
1041+ about_data = {}
1042+ # Group the same license information (name, url, file) together
1043+ license_key = []
1044+ license_name = []
1045+ license_file = []
1046+ license_url = []
1047+
10601048 for field in self .all_fields (with_absent , with_empty ):
1061- serialized .append (field .serialize ())
1062- # always end with a new line
1063- return u'\n ' .join (serialized ) + u'\n '
1049+ if field .name == 'license_key' :
1050+ license_key = field .value
1051+ elif field .name == 'license_name' :
1052+ license_name = field .value
1053+ elif field .name == 'license_file' :
1054+ license_file = field .value .keys ()
1055+ elif field .name == 'license_url' :
1056+ license_url = field .value
1057+ # No multiple 'about_resource' and 'about_resource_path' reference supported.
1058+ # Take the first element (should only be one) in the list for the
1059+ # value of 'about_resource' and 'about_resource_path'
1060+ elif field .name == 'about_resource' :
1061+ about_data [field .name ] = field .value [0 ]
1062+ elif field .name == 'about_resource_path' :
1063+ about_data [field .name ] = field .value .keys ()[0 ]
1064+ else :
1065+ about_data [field .name ] = field .value
1066+ # Group the same license information in a list
1067+ license_group = list (zip_longest (license_key , license_name , license_file , license_url ))
1068+ for lic_group in license_group :
1069+ lic_dict = {}
1070+ if lic_group [0 ] or with_empty :
1071+ lic_dict ['key' ] = lic_group [0 ]
1072+ if lic_group [1 ] or with_empty :
1073+ lic_dict ['name' ] = lic_group [1 ]
1074+ if lic_group [2 ] or with_empty :
1075+ lic_dict ['file' ] = lic_group [2 ]
1076+ if lic_group [3 ] or with_empty :
1077+ lic_dict ['url' ] = lic_group [3 ]
1078+ about_data .setdefault ('licenses' , []).append (lic_dict )
1079+ return saneyaml .dump (about_data )
10641080
10651081 def dump (self , location , with_absent = False , with_empty = True ):
10661082 """
0 commit comments