@@ -47,12 +47,8 @@ def logger_debug(*args):
4747
4848
4949@attr .s ()
50- class PHPComposerPackage (models .Package , models .PackageManifest ):
51- file_patterns = (
52- 'composer.json' ,
53- 'composer.lock' ,
54- )
55- extensions = ('.json' , '.lock' ,)
50+ class PHPComposerPackage (models .Package ):
51+
5652 mimetypes = ('application/json' ,)
5753
5854 default_type = 'composer'
@@ -61,11 +57,6 @@ class PHPComposerPackage(models.Package, models.PackageManifest):
6157 default_download_baseurl = None
6258 default_api_baseurl = 'https://packagist.org/p'
6359
64- @classmethod
65- def recognize (cls , location ):
66- for package in parse (location ):
67- yield package
68-
6960 @classmethod
7061 def get_package_root (cls , manifest_resource , codebase ):
7162 return manifest_resource .parent (codebase )
@@ -89,6 +80,157 @@ def compute_normalized_license(self):
8980 return compute_normalized_license (self .declared_license )
9081
9182
83+ @attr .s ()
84+ class PHPComposerJSON (PHPComposerPackage , models .PackageManifest ):
85+
86+ file_patterns = (
87+ 'composer.json' ,
88+ )
89+ extensions = ('.json' ,)
90+ manifest_type = 'composerjson'
91+
92+ @classmethod
93+ def is_manifest (cls , location ):
94+ """
95+ Return True if the file at ``location`` is likely a manifest of this type.
96+ """
97+ return filetype .is_file (location ) and fileutils .file_name (location ).lower () == 'composer.json'
98+
99+ @classmethod
100+ def recognize (cls , location ):
101+ """
102+ Yield one or more Package manifest objects given a file ``location`` pointing to a
103+ package archive, manifest or similar.
104+
105+ Note that this is NOT exactly the packagist .json format (all are closely related of
106+ course but have important (even if minor) differences.
107+ """
108+ with io .open (location , encoding = 'utf-8' ) as loc :
109+ package_data = json .load (loc )
110+
111+ yield cls .build_package_manifest (package_data )
112+
113+ @classmethod
114+ def build_package_manifest (cls , package_data ):
115+
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.
121+ # Therefore, we use "private-package-without-a-name" as a package name if
122+ # there is no name.
123+
124+ ns_name = package_data .get ('name' )
125+ is_private = False
126+ if not ns_name :
127+ ns = None
128+ name = 'private-package-without-a-name'
129+ is_private = True
130+ else :
131+ ns , _ , name = ns_name .rpartition ('/' )
132+
133+ package = PHPComposerPackage (
134+ namespace = ns ,
135+ name = name ,
136+ )
137+
138+ # mapping of top level composer.json items to the Package object field name
139+ plain_fields = [
140+ ('version' , 'version' ),
141+ ('description' , 'summary' ),
142+ ('keywords' , 'keywords' ),
143+ ('homepage' , 'homepage_url' ),
144+ ]
145+
146+ for source , target in plain_fields :
147+ value = package_data .get (source )
148+ if isinstance (value , str ):
149+ value = value .strip ()
150+ if value :
151+ setattr (package , target , value )
152+
153+ # mapping of top level composer.json items to a function accepting as
154+ # arguments the composer.json element value and returning an iterable of
155+ # key, values Package Object to update
156+ field_mappers = [
157+ ('authors' , author_mapper ),
158+ ('license' , partial (licensing_mapper , is_private = is_private )),
159+ ('support' , support_mapper ),
160+ ('require' , partial (_deps_mapper , scope = 'require' , is_runtime = True )),
161+ ('require-dev' , partial (_deps_mapper , scope = 'require-dev' , is_optional = True )),
162+ ('provide' , partial (_deps_mapper , scope = 'provide' , is_runtime = True )),
163+ ('conflict' , partial (_deps_mapper , scope = 'conflict' , is_runtime = True , is_optional = True )),
164+ ('replace' , partial (_deps_mapper , scope = 'replace' , is_runtime = True , is_optional = True )),
165+ ('suggest' , partial (_deps_mapper , scope = 'suggest' , is_runtime = True , is_optional = True )),
166+ ('source' , source_mapper ),
167+ ('dist' , dist_mapper )
168+ ]
169+
170+ for source , func in field_mappers :
171+ logger .debug ('parse: %(source)r, %(func)r' % locals ())
172+ value = package_data .get (source )
173+ if value :
174+ if isinstance (value , str ):
175+ value = value .strip ()
176+ if value :
177+ func (value , package )
178+ # Parse vendor from name value
179+ vendor_mapper (package )
180+ return package
181+
182+ @attr .s ()
183+ class PHPComposerLock (PHPComposerPackage , models .PackageManifest ):
184+
185+ file_patterns = (
186+ 'composer.lock' ,
187+ )
188+ extensions = ('.lock' ,)
189+ manifest_type = 'composerlock'
190+
191+ @classmethod
192+ def is_manifest (cls , location ):
193+ """
194+ Return True if the file at ``location`` is likely a manifest of this type.
195+ """
196+ return filetype .is_file (location ) and fileutils .file_name (location ).lower () == 'composer.lock'
197+
198+ @classmethod
199+ def recognize (cls , location ):
200+ """
201+ Yield one or more Package manifest objects given a file ``location`` pointing to a
202+ package archive, manifest or similar.
203+
204+ Note that this is NOT exactly the packagist .json format (all are closely related of
205+ course but have important (even if minor) differences.
206+ """
207+ with io .open (location , encoding = 'utf-8' ) as loc :
208+ package_data = json .load (loc )
209+
210+ packages = [
211+ PHPComposerJSON .build_package_manifest (p )
212+ for p in package_data .get ('packages' , [])
213+ ]
214+ packages_dev = [
215+ PHPComposerJSON .build_package_manifest (p )
216+ for p in package_data .get ('packages-dev' , [])
217+ ]
218+
219+ required_deps = [
220+ build_dep_package (p , scope = 'require' , is_runtime = True , is_optional = False )
221+ for p in packages
222+ ]
223+ required_dev_deps = [
224+ build_dep_package (p , scope = 'require-dev' , is_runtime = False , is_optional = True )
225+ for p in packages_dev
226+ ]
227+
228+ yield PHPComposerPackage (dependencies = required_deps + required_dev_deps )
229+
230+ for package in packages + packages_dev :
231+ yield package
232+
233+
92234def compute_normalized_license (declared_license ):
93235 """
94236 Return a normalized license expression string detected from a list of
@@ -121,103 +263,6 @@ def compute_normalized_license(declared_license):
121263 return combine_expressions (detected_licenses , 'OR' )
122264
123265
124- def is_phpcomposer_json (location ):
125- return filetype .is_file (location ) and fileutils .file_name (location ).lower () == 'composer.json'
126-
127-
128- def is_phpcomposer_lock (location ):
129- return filetype .is_file (location ) and fileutils .file_name (location ).lower () == 'composer.lock'
130-
131-
132- def parse (location ):
133- """
134- Yield Package objects from a composer.json or composer.lock file. Note that
135- this is NOT exactly the packagist .json format (all are closely related of
136- course but have important (even if minor) differences.
137- """
138- if is_phpcomposer_json (location ):
139- with io .open (location , encoding = 'utf-8' ) as loc :
140- package_data = json .load (loc )
141- yield build_package_from_json (package_data )
142-
143- elif is_phpcomposer_lock (location ):
144- with io .open (location , encoding = 'utf-8' ) as loc :
145- package_data = json .load (loc )
146- for package in build_packages_from_lock (package_data ):
147- yield package
148-
149-
150- def build_package_from_json (package_data ):
151- """
152- Return a composer Package object from a package data mapping or None.
153- """
154- # A composer.json without name and description is not a usable PHP
155- # composer package. Name and description fields are required but
156- # only for published packages:
157- # https://getcomposer.org/doc/04-schema.md#name
158- # We want to catch both published and non-published packages here.
159- # Therefore, we use "private-package-without-a-name" as a package name if
160- # there is no name.
161-
162- ns_name = package_data .get ('name' )
163- is_private = False
164- if not ns_name :
165- ns = None
166- name = 'private-package-without-a-name'
167- is_private = True
168- else :
169- ns , _ , name = ns_name .rpartition ('/' )
170-
171- package = PHPComposerPackage (
172- namespace = ns ,
173- name = name ,
174- )
175-
176- # mapping of top level composer.json items to the Package object field name
177- plain_fields = [
178- ('version' , 'version' ),
179- ('description' , 'summary' ),
180- ('keywords' , 'keywords' ),
181- ('homepage' , 'homepage_url' ),
182- ]
183-
184- for source , target in plain_fields :
185- value = package_data .get (source )
186- if isinstance (value , str ):
187- value = value .strip ()
188- if value :
189- setattr (package , target , value )
190-
191- # mapping of top level composer.json items to a function accepting as
192- # arguments the composer.json element value and returning an iterable of
193- # key, values Package Object to update
194- field_mappers = [
195- ('authors' , author_mapper ),
196- ('license' , partial (licensing_mapper , is_private = is_private )),
197- ('support' , support_mapper ),
198- ('require' , partial (_deps_mapper , scope = 'require' , is_runtime = True )),
199- ('require-dev' , partial (_deps_mapper , scope = 'require-dev' , is_optional = True )),
200- ('provide' , partial (_deps_mapper , scope = 'provide' , is_runtime = True )),
201- ('conflict' , partial (_deps_mapper , scope = 'conflict' , is_runtime = True , is_optional = True )),
202- ('replace' , partial (_deps_mapper , scope = 'replace' , is_runtime = True , is_optional = True )),
203- ('suggest' , partial (_deps_mapper , scope = 'suggest' , is_runtime = True , is_optional = True )),
204- ('source' , source_mapper ),
205- ('dist' , dist_mapper )
206- ]
207-
208- for source , func in field_mappers :
209- logger .debug ('parse: %(source)r, %(func)r' % locals ())
210- value = package_data .get (source )
211- if value :
212- if isinstance (value , str ):
213- value = value .strip ()
214- if value :
215- func (value , package )
216- # Parse vendor from name value
217- vendor_mapper (package )
218- return package
219-
220-
221266def licensing_mapper (licenses , package , is_private = False ):
222267 """
223268 Update package licensing and return package.
@@ -372,16 +417,3 @@ def build_dep_package(package, scope, is_runtime, is_optional):
372417 is_resolved = True ,
373418 )
374419
375-
376- def build_packages_from_lock (package_data ):
377- """
378- Yield composer Package objects from a package data mapping that originated
379- from a composer.lock file
380- """
381- packages = [build_package_from_json (p ) for p in package_data .get ('packages' , [])]
382- packages_dev = [build_package_from_json (p ) for p in package_data .get ('packages-dev' , [])]
383- required_deps = [build_dep_package (p , scope = 'require' , is_runtime = True , is_optional = False ) for p in packages ]
384- required_dev_deps = [build_dep_package (p , scope = 'require-dev' , is_runtime = False , is_optional = True ) for p in packages_dev ]
385- yield PHPComposerPackage (dependencies = required_deps + required_dev_deps )
386- for package in packages + packages_dev :
387- yield package
0 commit comments