11#!/usr/bin/env python
22# -*- coding: utf8 -*-
33
4+ # ============================================================================
5+ # Copyright (c) 2014 nexB Inc. http://www.nexb.com/ - All rights reserved.
6+ # Licensed under the Apache License, Version 2.0 (the "License");
7+ # you may not use this file except in compliance with the License.
8+ # You may obtain a copy of the License at
9+ # http://www.apache.org/licenses/LICENSE-2.0
10+ # Unless required by applicable law or agreed to in writing, software
11+ # distributed under the License is distributed on an "AS IS" BASIS,
12+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+ # See the License for the specific language governing permissions and
14+ # limitations under the License.
15+ # ============================================================================
16+
417"""
518AboutCode is a tool to process ABOUT files. ABOUT files are small text files
619that document the provenance (aka. the origin and license) of software
@@ -503,6 +516,7 @@ def check_network_connection():
503516 else :
504517 return True
505518
519+
506520has_network_connectivity = check_network_connection ()
507521
508522
@@ -528,6 +542,9 @@ def __init__(self, location=None):
528542 if self .location :
529543 self .parse ()
530544
545+ def __repr__ (self ):
546+ return repr ((self .parsed , self .parsed_fields , self .validated_fields ,))
547+
531548 def parse (self ):
532549 """
533550 Parse and validate a the file at self.location object in an ABOUT
@@ -1039,56 +1056,55 @@ def get_about_name(self):
10391056 """
10401057 return self .parsed .get ('name' , '' )
10411058
1042- class AboutCollector (object ):
1043- """
1044- A collection of AboutFile instances.
10451059
1046- Collects the About files in the given path on initialization.
1047- Creates one AboutFile instance per file.
1048- Summarize all the issues from each instance.
1060+ class Collector (object ):
10491061 """
1050- def __init__ (self , input_path ):
1051- if os .path .isdir (input_path ) and not input_path .endswith ('/' ):
1052- input_path = input_path + '/'
1053- self .user_provided_path = input_path
1054- self .absolute_path = os .path .abspath (input_path )
1055- assert os .path .exists (self .absolute_path )
1062+ Collect ABOUT files.
1063+ """
1064+ def __init__ (self , location ):
1065+ """
1066+ Collect ABOUT files at location and create one AboutFile instance per
1067+ file.
1068+ """
1069+ assert location
1070+ self .location = location
1071+ normed_loc = os .path .expanduser (location )
1072+ normed_loc = os .path .normpath (normed_loc )
1073+ normed_loc = os .path .abspath (normed_loc )
1074+ normed_loc = posix_path (normed_loc )
1075+ assert os .path .exists (normed_loc )
1076+ self .normalized_location = normed_loc
1077+ self .abouts = [AboutFile (f ) for f in self .collect (normed_loc )]
10561078
10571079 self ._errors = []
10581080 self ._warnings = []
1059-
10601081 self .genattrib_errors = []
1061-
1062- self .abouts = [AboutFile (f ) for f
1063- in self ._collect_about_files (self .absolute_path )]
1064-
1065- # self.create_about_objects_from_files()
1066- # self.extract_about_data_from_objects()
1067-
10681082 self .summarize_issues ()
10691083
10701084 def __iter__ (self ):
10711085 """
1072- Yield the collected about instances .
1086+ Iterate collected AboutFile .
10731087 """
10741088 return iter (self .abouts )
10751089
10761090 @staticmethod
1077- def _collect_about_files (location ):
1091+ def collect (location ):
10781092 """
1079- Given the location of a file or directory, return a list of
1080- locations of valid .ABOUT files.
1093+ Return a list of locations of *.ABOUT files given the location of an
1094+ ABOUT file or a directory tree containing ABOUT files.
1095+ Locations are normalized using posix path separators.
10811096 """
1097+ # FIXME: we should not accept both a file and dir location as input
10821098 paths = []
10831099 if location :
10841100 if os .path .isfile (location ) and is_about_file (location ):
1085- paths = [ location ]
1101+ paths . append ( location )
10861102 else :
10871103 for root , _ , files in os .walk (location ):
10881104 for name in files :
10891105 if is_about_file (name ):
10901106 paths .append (os .path .join (root , name ))
1091- # normalize the paths to use os path seps
1107+ # normalize the paths to use posix path separators
10921108 paths = [posix_path (p )for p in paths ]
10931109 return paths
10941110
@@ -1098,18 +1114,20 @@ def errors(self):
10981114 """
10991115 Return a list of about.errors for every about instances.
11001116 """
1117+ # FIXME: this function is not needed.
11011118 return self ._errors
11021119
11031120 @property
11041121 def warnings (self ):
11051122 """
11061123 Return a list of about.warnings for every about instances.
11071124 """
1125+ # FIXME: this function is not needed.
11081126 return self ._warnings
11091127
11101128 def summarize_issues (self ):
11111129 """
1112- Summarize and log, errors and warnings.
1130+ Summarize and log errors and warnings.
11131131 """
11141132 for about_object in self :
11151133 relative_path = self .get_relative_path (about_object .location )
@@ -1125,7 +1143,23 @@ def summarize_issues(self):
11251143 self ._warnings .extend (about_object .warnings )
11261144 logger .warning (about_object .warnings )
11271145
1128- def get_relative_path (self , about_object_location ):
1146+ def get_relative_path (self , location ):
1147+ """
1148+ Return a path for a given ABOUT file location relative to and based on
1149+ the provided collector normalized location.
1150+ """
1151+ user_loc = self .location
1152+ if os .path .isdir (self .normalized_location ):
1153+ subpath = location .partition (os .path .basename (os .path .normpath (user_loc )))[2 ]
1154+ if user_loc [- 1 ] == '/' :
1155+ user_loc = user_loc .rpartition ('/' )[0 ]
1156+ if user_loc [- 1 ] == '\\ ' :
1157+ user_loc = user_loc .rpartition ('\\ ' )[0 ]
1158+ return (user_loc + subpath ).replace ('\\ ' , '/' )
1159+ else :
1160+ return user_loc .replace ('\\ ' , '/' )
1161+
1162+ def get_relative_path2 (self , about_object_location ):
11291163 """
11301164 Return a relative path as provided by the user for an about_object.
11311165
@@ -1134,7 +1168,8 @@ def get_relative_path(self, about_object_location):
11341168 "hardcode" to add/append the path.
11351169 """
11361170 # FIXME: we should use correct path manipulation, not our own cooking
1137- user_provided_path = self .user_provided_path
1171+ # this is too complex
1172+ user_provided_path = self .location
11381173 if os .path .isdir (self .absolute_path ):
11391174 subpath = about_object_location .partition (
11401175 os .path .basename (os .path .normpath (user_provided_path )))[2 ]
@@ -1160,95 +1195,113 @@ def write_to_csv(self, output_path):
11601195 row_data = about_object .get_row_data (relative_path )
11611196 csv_writer .writerow (row_data )
11621197
1163- def generate_attribution (self , template_path = None , limit_to = None ):
1198+ def generate_attribution (self , template_path = 'templates/default.html' ,
1199+ limit_to = None ):
11641200 """
11651201 Generate an attribution file from the current list of ABOUT objects.
11661202 The optional `limit_to` parameter allows to restrict the generated
11671203 attribution to a specific list of component names.
11681204 """
1169- if not limit_to :
1170- limit_to = []
1171-
1172- if not template_path :
1173- template_path = 'templates/default.html'
11741205
11751206 try :
1176- import jinja2
1207+ import jinja2 as j2
11771208 except ImportError , e :
11781209 print ('The Jinja2 templating library is required to generate '
1179- 'attribution texts. You can install it with using :'
1180- 'pip install -r requirements.txt ' )
1210+ 'attribution texts. You can install it by running :'
1211+ 'configure ' )
11811212 return
11821213
1214+ # FIXME: the template dir should be outside the code tree
11831215 template_dir = os .path .dirname (template_path )
1184- template_name = os .path .basename (template_path )
1185- env = jinja2 .Environment (loader = jinja2 .FileSystemLoader (template_dir ))
1216+ template_file_name = os .path .basename (template_path )
1217+
1218+ jinja_env = j2 .Environment (loader = j2 .FileSystemLoader (template_dir ))
11861219
11871220 try :
1188- template = env .get_template (template_name )
1189- except jinja2 .TemplateNotFound :
1190- print ('Template: %(template_name )s not found' % locals ())
1221+ template = jinja_env .get_template (template_file_name )
1222+ except j2 .TemplateNotFound , e :
1223+ print ('Template: %(template_file_name )s not found' % locals ())
11911224 return
11921225
1226+ limit_to = limit_to or []
1227+ limit_to = set (limit_to )
1228+
11931229 about_object_fields = []
11941230 about_content_dict = {}
1195- not_exist_components = list (limit_to )
11961231
1197- # FIXME: this loop and conditional is too complex.
11981232 for about_object in self :
1199- about_relative_path = ('/' +
1200- about_object .location .partition (
1201- self .user_provided_path )[2 ])
1202-
1203- # Check is there any components in the 'limit_to' list that
1204- # does not exist in the code base.
1205- if limit_to :
1206- try :
1207- not_exist_components .remove (about_relative_path )
1208- except Exception as e :
1209- continue
1210-
1211- if not limit_to or about_relative_path in limit_to :
1212- about_content_dict = about_object .validated_fields
1213- # Add information in the dictionary where it does not
1214- # present in the ABOUT file
1215- about_content_dict ['license_text' ] = unicode (about_object .license_text (),
1216- errors = 'replace' )
1217- about_content_dict ['notice_text' ] = about_object .notice_text ()
1218-
1219- # FIXME: The following is a tmp code to handle multiple
1220- # 'license_text_file' in the input
1221- for k in about_content_dict :
1222- if '\n ' in about_content_dict [k ] and k == 'license_text_file' :
1223- about_content_dict ['license_text' ] = unicode (about_object .tmp_get_license_text (),
1224- errors = 'replace' )
1225-
1226- # Raise error if no license_text is found
1227- if not about_content_dict ['license_text' ]:
1228- msg = ('No license_text is found. '
1229- 'License generation is skipped.' )
1230- err = Error (GENATTRIB , 'name' ,
1231- about_object .get_about_name (), msg )
1232- self .genattrib_errors .append (err )
1233+ # FIXME: what is the meaning of this partition?
1234+ # PO created the var some_path to provide some clarity
1235+ # but what does the second element means?
1236+ some_path = about_object .location .partition (self .location )[2 ]
1237+ about_relative_path = '/' + some_path
1238+ print ('some_path:' , some_path )
1239+
1240+ if limit_to and about_relative_path in limit_to :
1241+ continue
1242+
1243+ about_content_dict = about_object .validated_fields
1244+ print ('about_content_dict:' , about_content_dict )
1245+ # Add information in the dictionary where it does not
1246+ # present in the ABOUT file
1247+ lic_text = unicode (about_object .license_text (),
1248+ errors = 'replace' )
1249+ about_content_dict ['license_text' ] = lic_text
1250+ notice_text = about_object .notice_text ()
1251+ about_content_dict ['notice_text' ] = notice_text
1252+
1253+ # FIXME: The following is a tmp code to handle multiple
1254+ # 'license_text_file' in the input
1255+ print ('about_content_dict:' , about_content_dict )
1256+ for k in about_content_dict :
1257+ if ('\n ' in about_content_dict [k ]
1258+ and k == 'license_text_file' ):
1259+ lic_text = unicode (about_object .tmp_get_license_text (),
1260+ errors = 'replace' )
1261+ about_content_dict ['license_text' ] = lic_text
1262+
1263+ # Raise error if no license_text is found
1264+ if 'license_text' not in about_content_dict :
1265+ msg = ('No license_text found. '
1266+ 'skipping License generation.' )
1267+ err = Error (GENATTRIB , 'name' ,
1268+ about_object .get_about_name (), msg )
1269+ self .genattrib_errors .append (err )
12331270
12341271 about_object_fields .append (about_content_dict )
12351272
1236- if not_exist_components :
1237- for component in not_exist_components :
1238- afp = self .user_provided_path + component .replace ('//' , '/' )
1239- msg = ('about file: %s - file does not exist. '
1240- 'No attribution is generated for this component.'
1241- % afp )
1242- err = Error (GENATTRIB , 'about_file' , component , msg )
1243- self .genattrib_errors .append (err )
1273+ # find paths requested in the limit_to paths arg that do not point to
1274+ # a corresponding ABOUT file
1275+ for path in limit_to :
1276+ path = posix_path (path )
1277+
1278+ afp = join (self .location , path )
1279+ msg = ('The requested ABOUT file: %(afp)r does not exist. '
1280+ 'No attribution generated for this file.' % locals ())
1281+ err = Error (GENATTRIB , 'about_file' , path , msg )
1282+ self .genattrib_errors .append (err )
12441283
12451284 # TODO: Handle the grouping and ordering later
12461285 """# We want to display common_licenses in alphabetical order
12471286 for key in sorted(common_license_dict.keys()):
12481287 license_key.append(key)
12491288 license_text_list.append(common_license_dict[key])"""
12501289
1251- return template .render (about_objects = about_object_fields )
1290+ rendered = template .render (about_objects = about_object_fields )
1291+ return rendered
1292+
1293+ def check_paths (self , paths ):
1294+ """
1295+ Check if each path in a list of ABOUT file paths exist in the
1296+ collected ABOUT files. Add errors if it does not.
1297+ """
1298+ for path in paths :
1299+ path = posix_path (path )
1300+ afp = join (self .location , path )
1301+ msg = ('The requested ABOUT file: %(afp)r does not exist. '
1302+ 'No attribution generated for this file.' % locals ())
1303+ err = Error (GENATTRIB , 'about_file' , path , msg )
1304+ self .genattrib_errors .append (err )
12521305
12531306 def get_genattrib_errors (self ):
12541307 return self .genattrib_errors
@@ -1320,7 +1373,7 @@ def main(parser, options, args):
13201373
13211374 if (not os .path .exists (output_path )
13221375 or (os .path .exists (output_path ) and overwrite )):
1323- collector = AboutCollector (input_path )
1376+ collector = Collector (input_path )
13241377 collector .write_to_csv (output_path )
13251378 if collector .errors :
13261379 print ('%d errors detected.' % len (collector .errors ))
0 commit comments