1+ # All rights reserved.
2+ # http://nexb.com and https://github.com/nexB/scancode-toolkit/
3+ # The ScanCode software is licensed under the Apache License version 2.0.
4+ # Data generated with ScanCode require an acknowledgment.
5+ # ScanCode is a trademark of nexB Inc.
6+ #
7+ # You may not use this software except in compliance with the License.
8+ # You may obtain a copy of the License at: http://apache.org/licenses/LICENSE-2.0
9+ # Unless required by applicable law or agreed to in writing, software distributed
10+ # under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
11+ # CONDITIONS OF ANY KIND, either express or implied. See the License for the
12+ # specific language governing permissions and limitations under the License.
13+ #
14+ # When you publish or redistribute any data created with ScanCode or any ScanCode
15+ # derivative work, you must accompany this data with the following acknowledgment:
16+ #
17+ # Generated with ScanCode and provided on an "AS IS" BASIS, WITHOUT WARRANTIES
18+ # OR CONDITIONS OF ANY KIND, either express or implied. No content created from
19+ # ScanCode should be considered or used as legal advice. Consult an Attorney
20+ # for any legal advice.
21+ # ScanCode is a free software code scanning tool from nexB Inc. and others.
22+ # Visit https://github.com/nexB/scancode-toolkit/ for support and download.
23+
24+ from __future__ import absolute_import
25+ from __future__ import print_function
26+ from __future__ import unicode_literals
27+
28+ import logging
29+ import re
30+
31+ import attr
32+ from packageurl import PackageURL
33+
34+ from commoncode .fileutils import py2
35+ from commoncode import filetype
36+ from commoncode import fileutils
37+ from packagedcode import models
38+ from packagedcode .spec import Spec
39+
40+
41+ """
42+ Handle cocoapods packages manifests for macOS and iOS
43+ including .podspec, Podfile and Podfile.lock files.
44+ See https://cocoapods.org
45+ """
46+
47+ # TODO: override the license detection to detect declared_license correctly.
48+
49+
50+ TRACE = False
51+
52+ logger = logging .getLogger (__name__ )
53+
54+ if TRACE :
55+ import sys
56+ logging .basicConfig (stream = sys .stdout )
57+ logger .setLevel (logging .DEBUG )
58+
59+
60+ @attr .s ()
61+ class CocoapodsPackage (models .Package ):
62+ metafiles = ('*.podspec' ,)
63+ extensions = ('.podspec' ,)
64+ default_type = 'pods'
65+ default_primary_language = 'Objective-C'
66+ default_web_baseurl = 'https://cocoapods.org'
67+ default_download_baseurl = None
68+ default_api_baseurl = None
69+
70+ @classmethod
71+ def recognize (cls , location ):
72+ yield parse (location )
73+
74+ def repository_homepage_url (self , baseurl = default_web_baseurl ):
75+ return '{}/pods/{}' .format (baseurl , self .name )
76+
77+ def repository_download_url (self ):
78+ return '{}/archive/{}.zip' .format (self .homepage_url , self .version )
79+
80+
81+ def is_podspec (location ):
82+ """
83+ Checks if the file is actually a podspec file
84+ """
85+ return (filetype .is_file (location ) and location .endswith ('.podspec' ))
86+
87+
88+ def parse (location ):
89+ """
90+ Return a Package object from a .podspec file or None.
91+ """
92+ if not is_podspec (location ):
93+ return
94+
95+ podspec_object = Spec ()
96+ podspec_data = podspec_object .parse_spec (location )
97+ return build_package (podspec_data )
98+
99+
100+ def build_package (podspec_data ):
101+ """
102+ Return a Package object from a package data mapping or None.
103+ """
104+ name = podspec_data .get ('name' )
105+ version = podspec_data .get ('version' )
106+ declared_license = podspec_data .get ('license' )
107+ summary = podspec_data .get ('summary' )
108+ description = podspec_data .get ('description' )
109+ homepage_url = podspec_data .get ('homepage_url' )
110+ source = podspec_data .get ('source' )
111+ authors = podspec_data .get ('author' ) or []
112+
113+ author_names = []
114+ author_email = []
115+ if authors :
116+ for split_author in authors :
117+ split_author = split_author .strip ()
118+ author , email = parse_person (split_author )
119+ author_names .append (author )
120+ author_email .append (email )
121+
122+ parties = list (party_mapper (author_names , author_email ))
123+
124+ package = CocoapodsPackage (
125+ name = name ,
126+ version = version ,
127+ vcs_url = source ,
128+ source_packages = list (source .split ('\n ' )),
129+ description = description ,
130+ declared_license = declared_license ,
131+ homepage_url = homepage_url ,
132+ parties = parties
133+ )
134+
135+ return package
136+
137+
138+ def party_mapper (author , email ):
139+ """
140+ Yields a Party object with author and email.
141+ """
142+ for person in author :
143+ yield models .Party (
144+ type = models .party_person ,
145+ name = person ,
146+ role = 'author' )
147+
148+ for person in email :
149+ yield models .Party (
150+ type = models .party_person ,
151+ email = person ,
152+ role = 'email' )
153+
154+
155+ person_parser = re .compile (
156+ r'^(?P<name>[\w\s(),-_.,]+)'
157+ r'=>'
158+ r'(?P<email>[\S+]+$)'
159+ ).match
160+
161+ person_parser_only_name = re .compile (
162+ r'^(?P<name>[\w\s(),-_.,]+)'
163+ ).match
164+
165+
166+ def parse_person (person ):
167+ """
168+ Return name and email from person string.
169+
170+ https://guides.cocoapods.org/syntax/podspec.html#authors
171+ Author can be in the form:
172+ s.author = 'Rohit Potter'
173+ or
174+ s.author = 'Rohit Potter=>rohit@gmail.com'
175+ Author check:
176+ >>> p = parse_person('Rohit Potter=>rohit@gmail.com')
177+ >>> assert p == ('Rohit Potter', 'rohit@gmail.com')
178+ >>> p = parse_person('Rohit Potter')
179+ >>> assert p == ('Rohit Potter', None)
180+ """
181+ parsed = person_parser (person )
182+ if not parsed :
183+ parsed = person_parser_only_name (person )
184+ name = parsed .group ('name' )
185+ email = None
186+ else :
187+ name = parsed .group ('name' )
188+ email = parsed .group ('email' )
189+
190+ return name , email
0 commit comments