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 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: implementation to get dependency data using gemsfileparser
48+ # Check: https://gitlab.com/balasankarc/gemfileparser
49+ # TODO: override the license detection to detect declared_license correctly.
50+
51+
52+ TRACE = False
53+
54+ logger = logging .getLogger (__name__ )
55+
56+ if TRACE :
57+ import sys
58+ logging .basicConfig (stream = sys .stdout )
59+ logger .setLevel (logging .DEBUG )
60+
61+
62+ @attr .s ()
63+ class CocoapodsPackage (models .Package ):
64+ metafiles = ('*.podspec' ,)
65+ extensions = ('.podspec' ,)
66+ default_type = 'pods'
67+ default_primary_language = 'Objective-C'
68+ default_web_baseurl = 'https://cocoapods.org'
69+ default_download_baseurl = None
70+ default_api_baseurl = None
71+
72+ @classmethod
73+ def recognize (cls , location ):
74+ yield parse (location )
75+
76+ def repository_homepage_url (self , baseurl = default_web_baseurl ):
77+ return '{}/pods/{}' .format (baseurl , self .name )
78+
79+ def repository_download_url (self ):
80+ return '{}/archive/{}.zip' .format (self .homepage_url , self .version )
81+
82+
83+ def is_podspec (location ):
84+ """
85+ Checks is the file is a podspec file or not.
86+ """
87+ return (filetype .is_file (location ) and location .endswith ('.podspec' ))
88+
89+
90+ def parse (location ):
91+ """
92+ Return a Package object from a .podspec file or None.
93+ """
94+ if not is_podspec (location ):
95+ return
96+
97+ podspec_data = spec .parse_spec (location )
98+ return build_package (podspec_data )
99+
100+
101+ def build_package (podspec_data ):
102+ """
103+ Return a Pacakge object from a package data mapping or None.
104+ """
105+ name = podspec_data .get ('name' )
106+ version = podspec_data .get ('version' )
107+ declared_license = podspec_data .get ('license' )
108+ summary = podspec_data .get ('summary' )
109+ description = podspec_data .get ('description' )
110+ homepage_url = podspec_data .get ('homepage_url' )
111+ source = podspec_data .get ('source' )
112+ authors = podspec_data .get ('author' ) or None
113+
114+ author_names = []
115+ author_email = []
116+ if authors :
117+ for split_author in authors :
118+ split_author = split_author .strip ()
119+ author , email = parse_person (split_author )
120+ author_names .append (author )
121+ author_email .append (email )
122+
123+ parties = []
124+ if authors :
125+ parties .append (
126+ models .Party (
127+ type = models .party_person ,
128+ name = ', ' .join (author_names ),
129+ email = ', ' .join (author_email ),
130+ role = 'author'
131+ )
132+ )
133+
134+ if len (summary ) > len (description ):
135+ description = summary
136+
137+ package = CocoapodsPackage (
138+ name = name ,
139+ version = version ,
140+ vcs_url = source ,
141+ source_packages = list (source .split ('\n ' )),
142+ description = description ,
143+ declared_license = declared_license ,
144+ homepage_url = homepage_url ,
145+ parties = parties
146+ )
147+ return package
148+
149+
150+ person_parser = re .compile (
151+ r'^(?P<name>[a-zA-Z0-9\s]+)'
152+ r'=>'
153+ r'(?P<email>[\S+]+$)'
154+ ).match
155+
156+ person_parser_only_name = re .compile (
157+ r'^(?P<name>[a-zA-Z0-9\s]+)'
158+ ).match
159+
160+
161+ def parse_person (person ):
162+ """
163+ https://guides.cocoapods.org/syntax/podspec.html#authors
164+ Author can be in the form:
165+ s.author = 'Rohit Potter'
166+ or
167+ s.author = 'Rohit Potter=>rohit@gmail.com'
168+ Author check:
169+ >>> p = parse_person('Rohit Potter=>rohit@gmail.com')
170+ >>> assert p == ('Rohit Potter', 'rohit@gmail.com')
171+ >>> p = parse_person('Rohit Potter')
172+ >>> assert p == ('Rohit Potter', None)
173+ """
174+ parsed = person_parser (person )
175+ if not parsed :
176+ parsed = person_parser_only_name (person )
177+ name = parsed .group ('name' )
178+ email = None
179+ else :
180+ name = parsed .group ('name' )
181+ email = parsed .group ('email' )
182+
183+ return name , email
0 commit comments