11#
2- # Copyright (c) 2015 nexB Inc. and others. All rights reserved.
2+ # Copyright (c) 2017 nexB Inc. and others. All rights reserved.
33# http://nexb.com and https://github.com/nexB/scancode-toolkit/
44# The ScanCode software is licensed under the Apache License version 2.0.
55# Data generated with ScanCode require an acknowledgment.
@@ -74,7 +74,24 @@ def parse(location):
7474 """
7575 if not is_phpcomposer_json (location ):
7676 return
77- # mapping of top level composer.json items to the Package object field name
77+
78+ with codecs .open (location , encoding = 'utf-8' ) as loc :
79+ package_data = json .load (loc , object_pairs_hook = OrderedDict )
80+
81+ base_dir = fileutils .parent_directory (location )
82+ metafile_name = fileutils .file_name (location )
83+
84+ return build_package (package_data , base_dir , metafile_name )
85+
86+
87+ def build_package (package_data , base_dir = None , metafile_name = 'composer.json' ):
88+ """
89+ Return a composer Package object from a package data mapping or
90+ None.
91+ """
92+
93+ # mapping of top level composer.json items to the Package object
94+ # field name
7895 plain_fields = OrderedDict ([
7996 ('name' , 'name' ),
8097 ('description' , 'summary' ),
@@ -83,8 +100,9 @@ def parse(location):
83100 ('homepage' , 'homepage_url' ),
84101 ])
85102
86- # mapping of top level composer.json items to a function accepting as arguments
87- # the composer.json element value and returning an iterable of key, values Package Object to update
103+ # mapping of top level composer.json items to a function accepting
104+ # as arguments the composer.json element value and returning an
105+ # iterable of key, values Package Object to update
88106 field_mappers = OrderedDict ([
89107 ('authors' , author_mapper ),
90108 ('license' , licensing_mapper ),
@@ -94,23 +112,20 @@ def parse(location):
94112 ('support' , support_mapper ),
95113 ])
96114
97- with codecs .open (location , encoding = 'utf-8' ) as loc :
98- data = json .load (loc , object_pairs_hook = OrderedDict )
99115
100- if not data .get ('name' ) or not data .get ('description' ):
101- # a composer.json without name and description is not a usable PHP composer package
102- # name and description fields are required: https://getcomposer.org/doc/04-schema.md#name
103- return
116+ # A composer.json without name and description is not a usable PHP
117+ # composer package. Name and description fields are required but
118+ # only for published packages:
119+ # https://getcomposer.org/doc/04-schema.md#name
120+ # We want to catch both published and non-published packages here.
104121
105122 package = PHPComposerPackage ()
106123 # a composer.json is at the root of a PHP composer package
107- base_dir = fileutils .parent_directory (location )
108124 package .location = base_dir
109- metafile = fileutils .file_name (location )
110- package .metafile_locations = [metafile ]
125+ package .metafile_locations = [metafile_name ]
111126
112127 for source , target in plain_fields .items ():
113- value = data .get (source )
128+ value = package_data .get (source )
114129 if value :
115130 if isinstance (value , basestring ):
116131 value = value .strip ()
@@ -119,13 +134,14 @@ def parse(location):
119134
120135 for source , func in field_mappers .items ():
121136 logger .debug ('parse: %(source)r, %(func)r' % locals ())
122- value = data .get (source )
137+ value = package_data .get (source )
123138 if value :
124139 if isinstance (value , basestring ):
125140 value = value .strip ()
126141 if value :
127142 func (value , package )
128- vendor_mapper (package ) # Parse vendor from name value
143+ # Parse vendor from name value
144+ vendor_mapper (package )
129145 return package
130146
131147
@@ -146,27 +162,25 @@ def licensing_mapper(licenses, package):
146162 if not licenses :
147163 return package
148164
149- if isinstance (licenses , basestring ):
150- package . asserted_licenses . append ( models . AssertedLicense ( license = licenses ))
151- elif isinstance ( licenses , list ):
165+ if isinstance (licenses , list ):
166+ # For a package, when there is a choice between licenses
167+ # ("disjunctive license"), multiple can be specified as array.
152168 """
153169 "license": [
154170 "LGPL-2.1",
155171 "GPL-3.0+"
156172 ]
157173 """
158- for lic in licenses :
159- if isinstance (lic , basestring ):
160- package .asserted_licenses .append (models .AssertedLicense (license = lic ))
161- else :
162- # use the bare repr
163- if lic :
164- package .asserted_licenses .append (models .AssertedLicense (license = repr (lic )))
174+ # build a proper license expression
175+ lics = [l .strip () for l in licenses if l and l .strip ()]
176+ lics = ' OR ' .join (lics )
165177
178+ elif not isinstance (licenses , basestring ):
179+ lics = repr (licenses )
166180 else :
167- # use the bare repr
168- package .asserted_licenses .append (models .AssertedLicense (license = repr (licenses )))
181+ lics = licenses
169182
183+ package .asserted_licenses .append (models .AssertedLicense (license = lics ))
170184 return package
171185
172186
@@ -187,22 +201,24 @@ def support_mapper(support, package):
187201 Update support and bug tracking url.
188202 https://getcomposer.org/doc/04-schema.md#support
189203 """
190- package .support_contacts = [support .get ('email' )]
191- package .bug_tracking_url = support .get ('issues' )
192- package .code_view_url = support .get ('source' )
204+ email = support .get ('email' )
205+ if email and email .strip ():
206+ package .support_contacts = [email ]
207+ package .bug_tracking_url = support .get ('issues' ) or None
208+ package .code_view_url = support .get ('source' ) or None
193209 return package
194210
195211
196212def vendor_mapper (package ):
197213 """
198- Vender is part of name element.
214+ Vendor is the first part of the name element.
199215 https://getcomposer.org/doc/04-schema.md#name
200216 """
201217 name = package .name
202218 if name and '/' in name :
203- vendors = name .split ('/' )
204- if vendors [ 0 ] :
205- package .vendors = [models .Party (name = vendors [ 0 ] )]
219+ vendor , _base_name = name .split ('/' , 1 )
220+ if vendor :
221+ package .vendors = [models .Party (name = vendor )]
206222 return package
207223
208224
@@ -327,6 +343,7 @@ def parse_person(persons):
327343 name = person .get ('name' )
328344 email = person .get ('email' )
329345 url = person .get ('homepage' )
346+ # FIXME: this got cargoculted from npm package.json parsing
330347 yield name and name .strip (), email and email .strip ('<> ' ), url and url .strip ('() ' )
331348 else :
332349 raise Exception ('Incorrect PHP composer composer.json person: %(person)r' % locals ())
0 commit comments