1
1
#
2
- # Copyright (c) 2015 nexB Inc. and others. All rights reserved.
2
+ # Copyright (c) 2017 nexB Inc. and others. All rights reserved.
3
3
# http://nexb.com and https://github.com/nexB/scancode-toolkit/
4
4
# The ScanCode software is licensed under the Apache License version 2.0.
5
5
# Data generated with ScanCode require an acknowledgment.
@@ -86,6 +86,20 @@ def parse(location):
86
86
if not is_package_json (location ):
87
87
return
88
88
89
+ with codecs .open (location , encoding = 'utf-8' ) as loc :
90
+ package_data = json .load (loc , object_pairs_hook = OrderedDict )
91
+
92
+ # a package.json is at the root of an NPM package
93
+ base_dir = fileutils .parent_directory (location )
94
+ metafile_name = fileutils .file_base_name (location )
95
+ return build_package (package_data , base_dir , metafile_name )
96
+
97
+
98
+ def build_package (package_data , base_dir = None , metafile_name = 'package.json' ):
99
+ """
100
+ Return a Package object from a package_data mapping (from a
101
+ package.json or similar) or None.
102
+ """
89
103
# mapping of top level package.json items to the Package object field name
90
104
plain_fields = OrderedDict ([
91
105
('name' , 'name' ),
@@ -101,33 +115,35 @@ def parse(location):
101
115
('bugs' , bugs_mapper ),
102
116
('contributors' , contributors_mapper ),
103
117
('maintainers' , maintainers_mapper ),
118
+ # current form
104
119
('license' , licensing_mapper ),
120
+ # old, deprecated form
105
121
('licenses' , licensing_mapper ),
106
122
('dependencies' , dependencies_mapper ),
107
123
('devDependencies' , dev_dependencies_mapper ),
108
124
('peerDependencies' , peer_dependencies_mapper ),
109
125
('optionalDependencies' , optional_dependencies_mapper ),
110
- ('url' , url_mapper ),
126
+ # legacy, ignored
127
+ # ('url', url_mapper),
111
128
('dist' , dist_mapper ),
112
129
('repository' , repository_mapper ),
113
130
])
114
131
115
- with codecs .open (location , encoding = 'utf-8' ) as loc :
116
- data = json .load (loc , object_pairs_hook = OrderedDict )
117
132
118
- if not data .get ('name' ) or not data .get ('version' ):
133
+ if not package_data .get ('name' ) or not package_data .get ('version' ):
119
134
# a package.json without name and version is not a usable NPM package
120
135
return
121
136
122
137
package = NpmPackage ()
123
138
# a package.json is at the root of an NPM package
124
- base_dir = fileutils .parent_directory (location )
125
139
package .location = base_dir
126
140
# for now we only recognize a package.json, not a node_modules directory yet
127
- package .metafile_locations = [location ]
128
- package .version = data .get ('version' )
141
+ if metafile_name :
142
+ package .metafile_locations = [metafile_name ]
143
+
144
+ package .version = package_data .get ('version' ) or None
129
145
for source , target in plain_fields .items ():
130
- value = data .get (source )
146
+ value = package_data .get (source ) or None
131
147
if value :
132
148
if isinstance (value , basestring ):
133
149
value = value .strip ()
@@ -136,14 +152,21 @@ def parse(location):
136
152
137
153
for source , func in field_mappers .items ():
138
154
logger .debug ('parse: %(source)r, %(func)r' % locals ())
139
- value = data .get (source )
155
+ value = package_data .get (source ) or None
140
156
if value :
141
157
if isinstance (value , basestring ):
142
158
value = value .strip ()
143
159
if value :
144
160
func (value , package )
145
- # this should be a mapper function but requires two args
146
- package .download_urls .append (public_download_url (package .name , package .version ))
161
+
162
+ # this should be a mapper function but requires two args.
163
+ # Note: we only add a synthetic download URL if there is none from
164
+ # the dist mapping.
165
+ if not package .download_urls :
166
+ tarball = public_download_url (package .name , package .version )
167
+ if tarball :
168
+ package .download_urls .append (tarball )
169
+
147
170
return package
148
171
149
172
@@ -152,7 +175,7 @@ def licensing_mapper(licenses, package):
152
175
Update package licensing and return package.
153
176
Licensing data structure has evolved over time and is a tad messy.
154
177
https://docs.npmjs.com/files/package.json#license
155
- licenses is either:
178
+ license(s) is either:
156
179
- a string with:
157
180
- an SPDX id or expression { "license" : "(ISC OR GPL-3.0)" }
158
181
- some license name or id
@@ -163,9 +186,13 @@ def licensing_mapper(licenses, package):
163
186
return package
164
187
165
188
if isinstance (licenses , basestring ):
189
+ # current form
190
+ # TODO: handle "SEE LICENSE IN <filename>"
191
+ # TODO: parse expression with license_expression library
166
192
package .asserted_licenses .append (models .AssertedLicense (license = licenses ))
167
193
168
194
elif isinstance (licenses , dict ):
195
+ # old, deprecated form
169
196
"""
170
197
"license": {
171
198
"type": "MIT",
@@ -176,6 +203,7 @@ def licensing_mapper(licenses, package):
176
203
url = licenses .get ('url' )))
177
204
178
205
elif isinstance (licenses , list ):
206
+ # old, deprecated form
179
207
"""
180
208
"licenses": ["type": "Apache License, Version 2.0",
181
209
"url": "http://www.apache.org/licenses/LICENSE-2.0" } ]
@@ -302,12 +330,19 @@ def repository_mapper(repo, package):
302
330
303
331
def url_mapper (url , package ):
304
332
"""
305
- In a package.json, the "url" field is a redirection to a package download
306
- URL published somewhere else than on the public npm registry.
307
- We map it to a download url.
333
+ In a package.json, the "url" field is a legacy field that contained
334
+ various URLs either as a string or as a mapping of type->url
308
335
"""
309
- if url :
310
- package .download_urls .append (url )
336
+ if not url :
337
+ return package
338
+
339
+ if isinstance (url , basestring ):
340
+ # TOOD: map to a miscellaneous urls dict
341
+ pass
342
+ elif isinstance (url , dict ):
343
+ # typical key is "web"
344
+ # TOOD: map to a miscellaneous urls dict
345
+ pass
311
346
return package
312
347
313
348
@@ -395,6 +430,11 @@ def parse_person(person):
395
430
Both forms are equivalent.
396
431
"""
397
432
# TODO: detect if this is a person name or a company name
433
+
434
+ name = None
435
+ email = None
436
+ url = None
437
+
398
438
if isinstance (person , basestring ):
399
439
parsed = person_parser (person )
400
440
if not parsed :
@@ -409,10 +449,28 @@ def parse_person(person):
409
449
name = person .get ('name' )
410
450
email = person .get ('email' )
411
451
url = person .get ('url' )
452
+
412
453
else :
413
454
raise Exception ('Incorrect NPM package.json person: %(person)r' % locals ())
414
455
415
- return name and name .strip (), email and email .strip ('<> ' ), url and url .strip ('() ' )
456
+ if name :
457
+ name = name .strip ()
458
+ if name .lower () == 'none' :
459
+ name = None
460
+ name = name or None
461
+
462
+ if email :
463
+ email = email .strip ('<> ' )
464
+ if email .lower () == 'none' :
465
+ email = None
466
+ email = email or None
467
+
468
+ if url :
469
+ url = url .strip ('() ' )
470
+ if url .lower () == 'none' :
471
+ url = None
472
+ url = url or None
473
+ return name , email , url
416
474
417
475
418
476
def public_download_url (name , version , registry = 'https://registry.npmjs.org' ):
0 commit comments